Files
TrueGrowth/docs/DRAWING_HAND_PANNING_LESSONS.md

4.6 KiB
Raw Blame History

绘制工具手形平移经验

更新日期2026-05-03

背景

画笔、钢笔、橡皮擦处于绘制过程中时,用户仍然期望可以临时移动画布:

  • 按住空格键拖动画布。
  • 按鼠标中键拖动画布。

这类交互属于画布导航能力,优先级应高于当前绘制工具的落点行为。否则用户想移动视图时,会误画线、误加钢笔锚点或误擦除元素。

根因

Plait 底层的 withHandPointer 已经支持两类平移入口:

  • 空格键会让画布容器进入 viewport-moving 状态。
  • 鼠标中键通过 isWheelPointer(event) 进入手形移动。

业务绘制插件位于 withHandPointer 外层,会先收到 pointerDown。如果绘制插件在中键或 viewport-moving 状态下直接启动绘制/擦除,就会抢走底层手形平移事件。

这次暴露出三个层面的缺口:

  1. 画笔、钢笔已有空格临时手形的透传逻辑,但没有显式覆盖中键。
  2. 橡皮擦缺少临时手形透传逻辑,空格和中键都可能误进入擦除。
  3. 物理 Pencil 模式会屏蔽非 Pencil 事件,也会把鼠标中键平移挡掉。

修复原则

绘制插件不要自己实现滚动或平移。

更稳的做法是:在绘制工具的 pointerDown 入口先判断当前事件是否应该交给手形平移插件;如果是,只把事件下放到底层 handler并在本插件内标记临时平移状态后续 pointerMovepointerUp 继续透传。

共享判断应收口到一个小工具函数,避免画笔、钢笔、橡皮擦各自复制条件:

export const shouldDelegateToHandPointer = (
  board: PlaitBoard,
  event: PointerEvent
) => {
  return isTemporaryHandMode(board) || isWheelPointer(event);
};

实现经验

1. 空格和中键要在同一层判断

不要只判断 viewport-moving

空格键在 pointerDown 前通常已经给容器加上 viewport-moving,所以绘制插件能识别;中键则要先把 pointerDown 交给底层 withHandPointer,底层才会设置中键移动状态。

因此绘制插件入口必须同时识别:

  • PlaitBoard.getBoardContainer(board).classList.contains('viewport-moving')
  • isWheelPointer(event)

2. 临时平移期间不要完成绘制动作

临时平移的 pointerUp 只应透传到底层,不应调用绘制完成逻辑:

  • 画笔不插入 Freehand
  • 钢笔不新增锚点、不完成路径。
  • 橡皮擦不调用删除或精确擦除。

globalPointerUp 要清理本插件的临时平移状态,避免指针在画布外抬起后状态残留。

3. Pencil 模式仍要放行画布导航

物理 Pencil 模式屏蔽鼠标绘制是合理的,但画布导航不是绘制输入。中键和空格临时手形应绕过 isPencilMode && !isPencilEvent 的屏蔽逻辑。

判断顺序建议:

  1. Pencil 事件可进入 Pencil 模式。
  2. 普通鼠标事件默认仍屏蔽。
  3. 中键或临时手形事件必须放行到底层。

4. 测试要覆盖“没有副作用”

这类修复不能只断言底层 handler 被调用,还要断言绘制副作用没有发生:

  • 画笔中键/空格平移时不调用 Transforms.insertNode
  • 橡皮擦中键/空格平移时不调用 CoreTransforms.removeElements
  • 钢笔中键平移时不新增锚点、不插入路径。
  • Pencil 模式下普通鼠标仍被屏蔽,中键和临时手形仍放行。

验证清单

  • 画笔绘制过程中,按住空格拖动画布,不产生新笔画。
  • 画笔绘制过程中,按鼠标中键拖动画布,不产生新笔画。
  • 钢笔绘制过程中,按住空格拖动画布,不新增锚点。
  • 钢笔绘制过程中,按鼠标中键拖动画布,不新增锚点。
  • 橡皮擦过程中,按住空格拖动画布,不删除元素。
  • 橡皮擦过程中,按鼠标中键拖动画布,不删除元素。
  • 物理 Pencil 模式下,普通鼠标仍不能绘制,中键/空格仍能移动画布。

相关文件

  • packages/drawnix/src/plugins/hand-mode.ts
  • packages/drawnix/src/plugins/freehand/with-freehand-create.ts
  • packages/drawnix/src/plugins/freehand/with-freehand-erase.ts
  • packages/drawnix/src/plugins/pen/with-pen-create.ts
  • packages/drawnix/src/plugins/with-pencil.ts
  • packages/drawnix/src/plugins/freehand/__tests__/hand-mode.test.ts
  • packages/drawnix/src/plugins/pen/__tests__/with-pen-create.test.ts
  • packages/drawnix/src/plugins/with-pencil.test.ts

建议验证命令

pnpm exec vitest run src/plugins/freehand/__tests__/hand-mode.test.ts src/plugins/pen/__tests__/with-pen-create.test.ts src/plugins/with-pencil.test.ts --config vitest.config.ts
pnpm exec tsc -p tsconfig.lib.json --noEmit