From d24954801360b93c7c42a698c44ee42d9f9391f6 Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 16 Jun 2026 16:48:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=BB=E5=B8=83=E5=B0=8F?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E6=8B=96=E6=8B=BD=E6=89=8B=E6=84=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 小地图拖拽改为基于按下时视图计算位移 降低小地图拖拽灵敏度并保留单击定位 补充反向拖拽不沿旧方向漂移的回归测试 --- .../ImageCanvasEditorView.test.tsx | 47 +++++++++++++++++++ .../image-editor/ImageCanvasEditorView.tsx | 47 ++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index 7c966ef2..f361a2e1 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -2192,6 +2192,53 @@ describe('ImageCanvasEditorView', () => { expect(screen.getByRole('button', { name: '画布小地图' })).toBeTruthy(); }); + it('keeps minimap drag direction stable after pausing and reversing', async () => { + await renderLoadedEditor(); + + const minimap = screen.getByRole('button', { name: '画布小地图' }); + vi.spyOn(minimap, 'getBoundingClientRect').mockReturnValue({ + x: 0, + y: 0, + left: 0, + top: 0, + right: 132, + bottom: 84, + width: 132, + height: 84, + toJSON: () => ({}), + }); + const world = screen + .getByLabelText('画布工作区') + .querySelector('.image-canvas-editor__world') as HTMLElement; + const readTranslateX = () => { + const match = /translate\(([-\d.]+)px,/u.exec(world.style.transform); + return match ? Number(match[1]) : 0; + }; + + dispatchPointerEvent(minimap, 'pointerdown', { + button: 0, + pointerId: 72, + clientX: 60, + clientY: 42, + }); + dispatchPointerEvent(screen.getByLabelText('画布工作区'), 'pointermove', { + button: 0, + pointerId: 72, + clientX: 120, + clientY: 42, + }); + const translateAfterRightDrag = readTranslateX(); + + dispatchPointerEvent(screen.getByLabelText('画布工作区'), 'pointermove', { + button: 0, + pointerId: 72, + clientX: 90, + clientY: 42, + }); + + expect(readTranslateX()).toBeGreaterThan(translateAfterRightDrag); + }); + it('persists layer groups in the canvas layer snapshot', async () => { await renderLoadedEditor(); diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx index fce14e00..f058e85f 100644 --- a/src/components/image-editor/ImageCanvasEditorView.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.tsx @@ -324,6 +324,11 @@ type DragState = | { kind: 'minimap'; pointerId: number; + startClientX: number; + startClientY: number; + startViewport: CanvasViewport; + minimapScale: number; + moved: boolean; }; const EDITOR_ASSET_FOLDERS: EditorAssetFolder[] = [ @@ -347,6 +352,7 @@ const SNAP_THRESHOLD_SCREEN_PX = 18; const FIT_VIEW_PADDING = 10; const MINIMAP_SIZE = { width: 132, height: 84 }; const MINIMAP_PADDING = 8; +const MINIMAP_DRAG_SENSITIVITY = 0.3; const CONTEXT_MENU_SIZE = { blank: { width: 176, height: 148 }, layer: { width: 176, height: 444 }, @@ -3519,6 +3525,28 @@ export function ImageCanvasEditorView() { })); }; + const moveViewportFromMinimapDrag = ( + dragState: Extract, + clientX: number, + clientY: number, + ) => { + const deltaWorldX = + ((clientX - dragState.startClientX) / dragState.minimapScale) * + MINIMAP_DRAG_SENSITIVITY; + const deltaWorldY = + ((clientY - dragState.startClientY) / dragState.minimapScale) * + MINIMAP_DRAG_SENSITIVITY; + setViewport({ + ...dragState.startViewport, + x: + dragState.startViewport.x - + deltaWorldX * dragState.startViewport.scale, + y: + dragState.startViewport.y - + deltaWorldY * dragState.startViewport.scale, + }); + }; + const handleMinimapPointerDown = ( event: ReactPointerEvent, ) => { @@ -3529,10 +3557,14 @@ export function ImageCanvasEditorView() { dragStateRef.current = { kind: 'minimap', pointerId: getPointerId(event), + startClientX: pointer.x, + startClientY: pointer.y, + startViewport: { ...viewportRef.current }, + minimapScale: minimapModel?.scale ?? 1, + moved: false, }; captureCanvasHistory(); dragHistoryCapturedRef.current = true; - moveViewportFromMinimapPointer(pointer.x, pointer.y); }; const handlePointerMove = (event: ReactPointerEvent) => { @@ -3625,7 +3657,14 @@ export function ImageCanvasEditorView() { dragHistoryCapturedRef.current = true; } const pointer = getPointerClient(event); - moveViewportFromMinimapPointer(pointer.x, pointer.y); + const deltaX = pointer.x - dragState.startClientX; + const deltaY = pointer.y - dragState.startClientY; + if (!dragState.moved && Math.hypot(deltaX, deltaY) >= 2) { + dragState.moved = true; + } + if (dragState.moved) { + moveViewportFromMinimapDrag(dragState, pointer.x, pointer.y); + } return; } @@ -3692,6 +3731,10 @@ export function ImageCanvasEditorView() { dragState && (dragState.pointerId < 0 || pointerId < 0 || dragState.pointerId === pointerId) ) { + if (dragState.kind === 'minimap' && !dragState.moved) { + const pointer = getPointerClient(event); + moveViewportFromMinimapPointer(pointer.x, pointer.y); + } dragStateRef.current = null; dragHistoryCapturedRef.current = false; setIsPanning(false);