4.6 KiB
绘制工具手形平移经验
更新日期:2026-05-03
背景
画笔、钢笔、橡皮擦处于绘制过程中时,用户仍然期望可以临时移动画布:
- 按住空格键拖动画布。
- 按鼠标中键拖动画布。
这类交互属于画布导航能力,优先级应高于当前绘制工具的落点行为。否则用户想移动视图时,会误画线、误加钢笔锚点或误擦除元素。
根因
Plait 底层的 withHandPointer 已经支持两类平移入口:
- 空格键会让画布容器进入
viewport-moving状态。 - 鼠标中键通过
isWheelPointer(event)进入手形移动。
业务绘制插件位于 withHandPointer 外层,会先收到 pointerDown。如果绘制插件在中键或 viewport-moving 状态下直接启动绘制/擦除,就会抢走底层手形平移事件。
这次暴露出三个层面的缺口:
- 画笔、钢笔已有空格临时手形的透传逻辑,但没有显式覆盖中键。
- 橡皮擦缺少临时手形透传逻辑,空格和中键都可能误进入擦除。
- 物理 Pencil 模式会屏蔽非 Pencil 事件,也会把鼠标中键平移挡掉。
修复原则
绘制插件不要自己实现滚动或平移。
更稳的做法是:在绘制工具的 pointerDown 入口先判断当前事件是否应该交给手形平移插件;如果是,只把事件下放到底层 handler,并在本插件内标记临时平移状态,后续 pointerMove、pointerUp 继续透传。
共享判断应收口到一个小工具函数,避免画笔、钢笔、橡皮擦各自复制条件:
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 的屏蔽逻辑。
判断顺序建议:
- Pencil 事件可进入 Pencil 模式。
- 普通鼠标事件默认仍屏蔽。
- 中键或临时手形事件必须放行到底层。
4. 测试要覆盖“没有副作用”
这类修复不能只断言底层 handler 被调用,还要断言绘制副作用没有发生:
- 画笔中键/空格平移时不调用
Transforms.insertNode。 - 橡皮擦中键/空格平移时不调用
CoreTransforms.removeElements。 - 钢笔中键平移时不新增锚点、不插入路径。
- Pencil 模式下普通鼠标仍被屏蔽,中键和临时手形仍放行。
验证清单
- 画笔绘制过程中,按住空格拖动画布,不产生新笔画。
- 画笔绘制过程中,按鼠标中键拖动画布,不产生新笔画。
- 钢笔绘制过程中,按住空格拖动画布,不新增锚点。
- 钢笔绘制过程中,按鼠标中键拖动画布,不新增锚点。
- 橡皮擦过程中,按住空格拖动画布,不删除元素。
- 橡皮擦过程中,按鼠标中键拖动画布,不删除元素。
- 物理 Pencil 模式下,普通鼠标仍不能绘制,中键/空格仍能移动画布。
相关文件
packages/drawnix/src/plugins/hand-mode.tspackages/drawnix/src/plugins/freehand/with-freehand-create.tspackages/drawnix/src/plugins/freehand/with-freehand-erase.tspackages/drawnix/src/plugins/pen/with-pen-create.tspackages/drawnix/src/plugins/with-pencil.tspackages/drawnix/src/plugins/freehand/__tests__/hand-mode.test.tspackages/drawnix/src/plugins/pen/__tests__/with-pen-create.test.tspackages/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