优化画布小地图拖拽手感
小地图拖拽改为基于按下时视图计算位移 降低小地图拖拽灵敏度并保留单击定位 补充反向拖拽不沿旧方向漂移的回归测试
This commit is contained in:
@@ -2192,6 +2192,53 @@ describe('ImageCanvasEditorView', () => {
|
|||||||
expect(screen.getByRole('button', { name: '画布小地图' })).toBeTruthy();
|
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 () => {
|
it('persists layer groups in the canvas layer snapshot', async () => {
|
||||||
await renderLoadedEditor();
|
await renderLoadedEditor();
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,11 @@ type DragState =
|
|||||||
| {
|
| {
|
||||||
kind: 'minimap';
|
kind: 'minimap';
|
||||||
pointerId: number;
|
pointerId: number;
|
||||||
|
startClientX: number;
|
||||||
|
startClientY: number;
|
||||||
|
startViewport: CanvasViewport;
|
||||||
|
minimapScale: number;
|
||||||
|
moved: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EDITOR_ASSET_FOLDERS: EditorAssetFolder[] = [
|
const EDITOR_ASSET_FOLDERS: EditorAssetFolder[] = [
|
||||||
@@ -347,6 +352,7 @@ const SNAP_THRESHOLD_SCREEN_PX = 18;
|
|||||||
const FIT_VIEW_PADDING = 10;
|
const FIT_VIEW_PADDING = 10;
|
||||||
const MINIMAP_SIZE = { width: 132, height: 84 };
|
const MINIMAP_SIZE = { width: 132, height: 84 };
|
||||||
const MINIMAP_PADDING = 8;
|
const MINIMAP_PADDING = 8;
|
||||||
|
const MINIMAP_DRAG_SENSITIVITY = 0.3;
|
||||||
const CONTEXT_MENU_SIZE = {
|
const CONTEXT_MENU_SIZE = {
|
||||||
blank: { width: 176, height: 148 },
|
blank: { width: 176, height: 148 },
|
||||||
layer: { width: 176, height: 444 },
|
layer: { width: 176, height: 444 },
|
||||||
@@ -3519,6 +3525,28 @@ export function ImageCanvasEditorView() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const moveViewportFromMinimapDrag = (
|
||||||
|
dragState: Extract<DragState, { kind: 'minimap' }>,
|
||||||
|
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 = (
|
const handleMinimapPointerDown = (
|
||||||
event: ReactPointerEvent<HTMLButtonElement>,
|
event: ReactPointerEvent<HTMLButtonElement>,
|
||||||
) => {
|
) => {
|
||||||
@@ -3529,10 +3557,14 @@ export function ImageCanvasEditorView() {
|
|||||||
dragStateRef.current = {
|
dragStateRef.current = {
|
||||||
kind: 'minimap',
|
kind: 'minimap',
|
||||||
pointerId: getPointerId(event),
|
pointerId: getPointerId(event),
|
||||||
|
startClientX: pointer.x,
|
||||||
|
startClientY: pointer.y,
|
||||||
|
startViewport: { ...viewportRef.current },
|
||||||
|
minimapScale: minimapModel?.scale ?? 1,
|
||||||
|
moved: false,
|
||||||
};
|
};
|
||||||
captureCanvasHistory();
|
captureCanvasHistory();
|
||||||
dragHistoryCapturedRef.current = true;
|
dragHistoryCapturedRef.current = true;
|
||||||
moveViewportFromMinimapPointer(pointer.x, pointer.y);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
|
const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||||
@@ -3625,7 +3657,14 @@ export function ImageCanvasEditorView() {
|
|||||||
dragHistoryCapturedRef.current = true;
|
dragHistoryCapturedRef.current = true;
|
||||||
}
|
}
|
||||||
const pointer = getPointerClient(event);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3692,6 +3731,10 @@ export function ImageCanvasEditorView() {
|
|||||||
dragState &&
|
dragState &&
|
||||||
(dragState.pointerId < 0 || pointerId < 0 || dragState.pointerId === pointerId)
|
(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;
|
dragStateRef.current = null;
|
||||||
dragHistoryCapturedRef.current = false;
|
dragHistoryCapturedRef.current = false;
|
||||||
setIsPanning(false);
|
setIsPanning(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user