diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 6135a4b7d..81a70bcaf 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4370,5 +4370,5 @@ ## 2026-07-21 图片画布滚轮与中键平移统一为二维视口移动 - 背景:画布中键拖拽的平移模型已同时计算 X / Y,但普通滚轮分支只消费 `deltaY`,横向滚轮或触控板的 `deltaX` 被丢弃,且缺少中键横向拖动的状态机回归覆盖。 -- 决策:普通滚轮原样消费设备上报的 `deltaX / deltaY` 二维平移 viewport,不在核心平移模型内根据修饰键重映射轴向。`Ctrl / Cmd + 滚轮` 继续只负责围绕指针缩放;中键和抓手拖拽继续同时更新 X / Y。 -- 验证:交互模型单测覆盖原始 `deltaX / deltaY` 和缩放边界;viewport hook 单测覆盖二维滚轮与 Ctrl 缩放;stage 状态机单测覆盖中键水平、垂直同时移动。 +- 决策:普通滚轮原样消费设备上报的 `deltaX / deltaY` 二维平移 viewport;当按住 Shift 且设备上报 `deltaX = 0` 时,输入适配层把 `deltaY` 映射为横向位移并将纵向位移置零,核心平移模型不感知修饰键。`Ctrl / Cmd + 滚轮` 继续只负责围绕指针缩放;中键和抓手拖拽继续同时更新 X / Y。 +- 验证:交互模型单测覆盖原始 `deltaX / deltaY` 和缩放边界;viewport hook 单测覆盖二维滚轮、Shift 横向适配与 Ctrl 缩放;stage 状态机单测覆盖中键水平、垂直同时移动。 diff --git a/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md b/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md index 7d15c9384..cca6665d0 100644 --- a/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md +++ b/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md @@ -149,7 +149,7 @@ - 素材面板支持按素材名、文件夹名、生成信息、模型、任务和媒体类型搜索,并支持选择模式框选,一次选中多个素材,并可批量移动或删除上传素材。 - 图层面板支持按图层名、生成信息、模型、任务和媒体类型搜索;支持选择多个图层后创建图层组,组名和 groupId 随画布布局保存。 - 小地图支持拖拽视口框,拖动时画布 viewport 跟随移动;`pointermove` 更新必须通过 `requestAnimationFrame` 合帧,结束拖拽时 flush 最后一帧,避免高频 pointermove 直接压垮 React 渲染和项目持久化链路。 -- 鼠标滚轮默认按设备原始 `deltaX / deltaY` 二维平移画布视口,不在画布交互模型内做轴向重映射。按住 Ctrl / Cmd 滚轮才缩放画布,并阻止浏览器页面缩放。缩放比例显示保持现有换算口径,最低可缩小到 `5%`。 +- 鼠标滚轮默认按设备原始 `deltaX / deltaY` 二维平移画布视口;当按住 Shift 且设备上报 `deltaX = 0` 时,视口输入适配层把 `deltaY` 映射为横向位移并将纵向位移置零,核心平移模型仍只消费归一化后的 X / Y。按住 Ctrl / Cmd 滚轮才缩放画布,并阻止浏览器页面缩放。缩放比例显示保持现有换算口径,最低可缩小到 `5%`。 - 工程刷新后能从后端恢复资源、图层布局和 viewport。 - “我的”页项目入口能进入 `/project`;项目页能列出工程、重命名 / 删除单个工程、批量选择和批量删除;点击工程后进入 `/editor/canvas?projectid=` 并按 query 加载该工程。 diff --git a/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md b/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md index a2020440d..68c306b07 100644 --- a/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md +++ b/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md @@ -68,7 +68,7 @@ ## 第六阶段模块 - `ImageCanvasInteractionModel.ts` - - 承载画布交互纯计算:适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。 + - 承载画布交互纯计算:适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Shift 且 `deltaX = 0` 时由输入适配层把 `deltaY` 映射为横向位移、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。 - 主视图继续负责 React 事件对象、pointer capture、history 快照、生成对象回写、选中态和 `setState`。 - 该模块用独立单测覆盖小地图灵敏度、吸附、多选拖拽和滚轮缩放等之前容易回退的交互规则。 @@ -151,7 +151,7 @@ ## 第十七阶段模块 - `useImageCanvasViewportControls.ts` - - 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。 + - 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮按原始 `deltaX / deltaY` 二维平移、Shift 且 `deltaX = 0` 时的横向位移适配、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。 - 主视图继续负责图层拖拽、生成占位框拖拽、框选、多选、历史触发时机、上传 drop 分流和小地图 pointer down 事件;该 hook 只作为视口控制协调器,不接管画布完整 pointer 状态机。 - 该 hook 用独立单测覆盖尺寸同步、适合视图、中心缩放、坐标换算、滚轮语义和小地图移动,为后续抽 `useImageCanvasStageInteractions` 预留更清晰的视口接口。 diff --git a/src/components/image-editor/ImageCanvasShortcutModel.ts b/src/components/image-editor/ImageCanvasShortcutModel.ts index 896d869d7..7863e599a 100644 --- a/src/components/image-editor/ImageCanvasShortcutModel.ts +++ b/src/components/image-editor/ImageCanvasShortcutModel.ts @@ -30,6 +30,7 @@ export const IMAGE_CANVAS_SHORTCUT_SECTIONS: ImageCanvasShortcutSection[] = [ title: '视图', items: [ { action: '平移画布', keys: ['鼠标滚轮 / 触控板'], status: 'ready' }, + { action: '横向平移画布', keys: ['Shift', '鼠标滚轮'], status: 'ready' }, { action: '以鼠标位置缩放', keys: ['Ctrl', '鼠标滚轮'], status: 'ready' }, { action: '显示全部元素', keys: ['Ctrl', '0'], status: 'new' }, { action: '缩放至 100%', keys: ['Ctrl', '1'], status: 'new' }, diff --git a/src/components/image-editor/useImageCanvasViewportControls.test.tsx b/src/components/image-editor/useImageCanvasViewportControls.test.tsx index 556526b69..0f0ab01eb 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.test.tsx +++ b/src/components/image-editor/useImageCanvasViewportControls.test.tsx @@ -158,7 +158,40 @@ describe('useImageCanvasViewportControls', () => { expect(captureCanvasHistory).toHaveBeenCalledTimes(2); }); - it('handles two-axis wheel pan, ctrl wheel zoom and minimap movement', () => { + it('keeps ordinary mouse wheel vertical and maps Shift wheel to horizontal', () => { + const { result, viewportElement } = renderViewportControls(); + + act(() => { + fireEvent.wheel(viewportElement, { + deltaX: 0, + deltaY: 120, + clientX: 260, + clientY: 190, + }); + }); + expectViewport(result.current.viewport, { + x: -260, + y: -50, + scale: 0.5, + }); + + act(() => { + fireEvent.wheel(viewportElement, { + shiftKey: true, + deltaX: 0, + deltaY: 80, + clientX: 260, + clientY: 190, + }); + }); + expectViewport(result.current.viewport, { + x: -340, + y: -50, + scale: 0.5, + }); + }); + + it('handles native two-axis wheel pan, ctrl wheel zoom and minimap movement', () => { const rafCallbacks: FrameRequestCallback[] = []; vi.spyOn(window, 'requestAnimationFrame').mockImplementation( (callback: FrameRequestCallback) => { diff --git a/src/components/image-editor/useImageCanvasViewportControls.ts b/src/components/image-editor/useImageCanvasViewportControls.ts index 46d39a9f7..c47deb11d 100644 --- a/src/components/image-editor/useImageCanvasViewportControls.ts +++ b/src/components/image-editor/useImageCanvasViewportControls.ts @@ -260,11 +260,15 @@ export function useImageCanvasViewportControls({ } if (!event.ctrlKey && !event.metaKey) { + const useShiftWheelHorizontalFallback = + event.shiftKey && event.deltaX === 0; setViewport((currentViewport) => panViewportFromWheel({ viewport: currentViewport, - deltaX: event.deltaX, - deltaY: event.deltaY, + deltaX: useShiftWheelHorizontalFallback + ? event.deltaY + : event.deltaX, + deltaY: useShiftWheelHorizontalFallback ? 0 : event.deltaY, }), ); return;