news 2026/9/28 3:04:17

掌握 OpenPencil Vue SDK 的 useCanvasInput:画布指针交互中枢的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
掌握 OpenPencil Vue SDK 的 useCanvasInput:画布指针交互中枢的源码级解析
  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载

导读

useCanvasInput是 OpenPencil(AI-native 设计编辑器开源项目)Vue SDK 中负责把<canvas>元素的指针与鼠标事件接入编辑器输入系统的核心 composable。它把屏幕坐标换算成画布坐标、统一调度选中、拖拽、缩放、旋转、平移、绘图工具与文本/矢量编辑等全部画布交互,是任何自建编辑器外壳(editor shell)接入画布时必须掌握的低层 API。读完本文,你将理解useCanvasInput的完整签名、各参数的职责、内部交互状态机(DragState)、坐标换算原理以及它在真实编辑器外壳中的调用方式,能够据此在自己构建的 Vue 组件里正确接入 OpenPencil 画布。

本文基于仓库中该 API 的官方文档(packages/docs/fr/programmable/sdk/api/composables/use-canvas-input.md),并深入其实际源码 packages/vue/src/canvas/useCanvasInput.ts 及配套输入模块展开。

一、useCanvasInput 是什么

根据官方文档,useCanvasInput(options)把目标元素(通常是<canvas>)的事件绑定到编辑器的输入系统上,覆盖的能力清单包括:

  • 指针的移动、按下与释放(mouvement, pression et relâchement du pointeur);
  • 选中与框选(sélection et rectangle de sélection);
  • 视图平移与缩放(déplacement de la vue et zoom);
  • 对象拖拽(glissement des objets);
  • 缩放与旋转(redimensionnement et rotation);
  • 形状工具、钢笔(Plume)、文本与抓手工具(outils de formes, Plume, Texte et Main);
  • 矢量编辑与文本编辑(édition vectorielle et textuelle)。

文档同时强调了两个关键设计:

  1. 坐标换算:该 composable 会把屏幕坐标转换为画布坐标,并在一次交互的整个生命周期内保持指针捕获(pointer capture);
  2. 定位:这是一个低层(bas niveau)API,主要面向“自己拥有画布、希望构建干净界面”的编辑器外壳组件,而非高层封装。

在 Vue SDK 中,useCanvasInput与useCanvas(负责 CanvasKit 渲染表面的创建与维护,见 use-canvas.md)和useTextEdit(负责文本输入,见 use-text-edit.md)三者通常搭配使用:useCanvas管渲染,useCanvasInput管交互,useTextEdit管画布内文本编辑。它在 SDK 公开入口 packages/vue/src/index.ts 中被导出。

二、函数签名与参数说明

虽然法语文档以useCanvasInput(options)概括,实际源码 packages/vue/src/canvas/useCanvasInput.ts 中的签名更为精确,是理解其行为的权威依据:

export function useCanvasInput( canvasRef: Ref<HTMLCanvasElement | null>, editor: Editor, hitTestSectionTitle: (cx: number, cy: number) => SceneNode | null, hitTestComponentLabel: (cx: number, cy: number) => SceneNode | null, hitTestFrameTitle: (cx: number, cy: number) => SceneNode | null, onCursorMove?: (cx: number, cy: number) => void, onActivate?: () => void, isEnabled: () => boolean = () => true )

各参数含义如下:

参数类型说明
canvasRefRef<HTMLCanvasElement \| null>目标画布元素的引用,所有事件都绑定在此元素上
editorEditor来自@open-pencil/core/editor的编辑器实例(通常由createEditor创建、useEditor从上下文取回)
hitTestSectionTitle(cx, cy) => SceneNode \| null命中测试:给定画布坐标,返回命中的 Section 标题节点
hitTestComponentLabel(cx, cy) => SceneNode \| null命中测试:返回命中的组件标签节点
hitTestFrameTitle(cx, cy) => SceneNode \| null命中测试:返回命中的 Frame 标题节点
onCursorMove可选回调指针移动时回调,参数为画布坐标(cx, cy),可用于同步外部悬浮信息
onActivate可选回调每次mousedown时触发,编辑器外壳用它做“激活面板”等处理
isEnabled() => boolean交互开关(默认恒为true),为false时忽略输入;多面板场景下用于区分当前活动面板

返回对象包含供外壳组件消费的状态与操作(源码 useCanvasInput.ts):

  • drag:当前拖拽状态(Ref<DragState | null>),用于驱动覆盖层 UI;
  • cursorOverride:Ref<string | null>,画布上的光标覆盖值;
  • 画布标签(Frame/Section 标题)编辑:canvasLabelEdit、updateCanvasLabelEdit、commitCanvasLabelEdit、cancelCanvasLabelEdit;
  • Auto Layout 内边距编辑:autoLayoutPaddingEdit、updateAutoLayoutPaddingEdit、commitAutoLayoutPaddingEdit、cancelAutoLayoutPaddingEdit;
  • cleanupInteractions():取消进行中的交互、清空临时反馈并复位修饰键状态,常用于面板失活或组件卸载时。

注意:法语文档把参数概括为options对象,德语版文档则给出位置参数形式的调用示例(见 packages/docs/de/programmable/sdk/api/composables/use-canvas-input.md),源码实现采用的是上面这组位置参数。

三、坐标换算:从屏幕坐标到画布坐标

文档明确提及“composable 把屏幕坐标转换成画布坐标”。这一逻辑集中在 packages/vue/src/shared/input/geometry.ts 的getPointerCoords中:

export function getPointerCoords(e: MouseEvent, canvas: HTMLCanvasElement | null, editor: Editor) { if (!canvas) return { sx: 0, sy: 0, cx: 0, cy: 0 } const rect = canvas.getBoundingClientRect() const sx = e.clientX - rect.left const sy = e.clientY - rect.top const { x: cx, y: cy } = editor.screenToCanvas(sx, sy) return { sx, sy, cx, cy } }

换算分两步:

  1. 客户端坐标 → 画布像素坐标:用getBoundingClientRect()减去画布左上角,得到sx/sy;
  2. 画布像素坐标 → 画布逻辑坐标:通过editor.screenToCanvas(sx, sy)得到cx/cy。screenToCanvas内部会考虑编辑器的平移(pan)与缩放(zoom),因此useCanvasInput处理的永远是逻辑坐标,缩放平移无需你手工处理。

此外,packages/vue/src/canvas/pointer/use.ts 的createCanvasPointer在换算基础上又封装了三类能力:canvasToLocal(画布坐标 → 某个节点局部坐标,供进入容器内部后的命中测试使用)、hitTestInScope(在当前“进入的容器”或当前页范围内做命中测试,支持深/浅两种模式)、isInsideContainerBounds(判断某点是否落在容器边界内)。这三者与三个标题命中测试函数一起组成了hitFns,供拖拽调度与光标更新共用。

四、统一拖拽状态机:DragState

useCanvasInput的核心是一个dragref,所有进行中的交互都归约为统一的拖拽状态联合类型DragState,定义于 packages/vue/src/shared/input/types.ts:

export type DragState = | DragDraw // 绘图工具拖拽(矩形、椭圆、线条……) | DragMove // 移动选中对象 | DragPan // 抓手/空格平移视图 | DragResize // 缩放 | DragMarquee // 框选 | DragRotate // 旋转 | DragPen // 钢笔路径绘制 | DragTextSelect // 文本选区 | DragEditNode // 矢量节点编辑 | DragEditHandle // 贝塞尔手柄编辑 | DragBendHandle // 弯曲手柄编辑 | DragGuide // 标尺/参考线拖拽

以DragMove为例(types.ts),它记录起点、当前位置、已应用的位移增量(appliedDx/appliedDy)、是否已判定为拖拽(dragStarted)、所有对象的原始位置,以及duplicated(拖拽时按住修饰键复制)、autoLayoutParentId、brokeFromAutoLayout等自动布局相关信息。DragResize(types.ts)则保存origRect、原矢量网络、填充/描边几何、派生文本字形等快照,保证缩放预览可随时回滚。

onMouseMove(useCanvasInput.ts)是一个集中的分发器:根据drag.value.type把移动事件分别交给handlePanMove、guideInput.handleMove、handleRotateMove、handleMoveMove、applyResize、handlePenDragMove、handleNodeEditMove、handleBendHandleMove、handleDrawMove、handleMarqueeMove等实现。无拖拽时它仍会驱动钢笔悬浮(updatePenHover)、矢量节点悬浮(updateNodeEditHover)、参考线悬浮与光标更新,保证“仅悬停不拖拽”的场景也有即时反馈。

onMouseUp(useCanvasInput.ts)按类型提交结果:commitResizePreview提交缩放、editor.penCommit(true)提交钢笔路径、editor.commitRotation提交旋转、draw.commit()提交绘图、setMarquee(null)结束框选。这样所有交互都遵循“开始 → 预览 → 提交/取消”的同一生命周期。

五、指针捕获与事件绑定

文档强调“在交互期间保持指针捕获”。这一实现位于 useCanvasInput.ts:

function onPointerDown(e: PointerEvent) { if (e.pointerType !== 'mouse' || e.button !== 0) return canvasRef.value?.setPointerCapture(e.pointerId) }
  • pointerdown时若为鼠标主键,立即调用setPointerCapture,保证即使指针移出画布,后续的mousemove/mouseup仍派发到画布,拖拽不中断;
  • pointerup/pointercancel时释放捕获并分别走“提交”与“取消”路径。

事件绑定通过useEventListener完成(useCanvasInput.ts),包括画布上的pointerdown/pointerup/pointercancel/dblclick/mousedown/mousemove/mouseup/mouseleave,以及 window 级的keydown/keyup(追踪 Alt/Meta/Ctrl 修饰键)、blur(失焦时复位测量修饰键并取消交互)、捕获阶段的mouseup(防止指针离开画布后拖拽卡死)和捕获阶段的Escape(取消绘图/旋转)。

另外它还订阅了编辑器的若干事件(useCanvasInput.ts):rotation:preview-changed、tool:changed、selection:changed、page:changed、graph:replaced。当工具切换、选区变化或页面/图被替换时,进行中的绘图与旋转交互会被自动取消,避免状态错乱;组件卸载时(onScopeDispose)会统一停止监听并调用cancelPointerInteraction。

六、工具联动与修饰键语义

onMouseDown(useCanvasInput.ts)是交互入口:先触发onActivate,再依据isEnabled判断是否处理;随后提交可能存在的 Auto Layout 内边距编辑、聚焦画布、清空悬浮节点,并在参考线拖拽(标尺拖出/已有参考线)之后,把事件交给handleToolMouseDown统一派发。工具与节点类型的映射TOOL_TO_NODE(types.ts)说明绘图工具(FRAME/SECTION/RECTANGLE/ELLIPSE/LINE/POLYGON/STAR/TEXT)会创建对应类型的节点。

修饰键在交互中有明确语义,由updateModifier跟踪(useCanvasInput.ts),并在移动/释放分支中生效:

  • Shift:缩放保持宽高比、旋转按角度约束、钢笔切线约束;
  • Alt:测量模式开关(配合画布内测量)、参考线复制拖拽、节点编辑分支调整;
  • Meta/Ctrl:测量模式升级为“deep”、拖拽复制、节点编辑分支的精确控制;
  • 空格:临时切换到抓手平移(setupPanZoom与useSpaceHeld配合实现)。

值得留意的是测量模式(measurement):canMeasure(useCanvasInput.ts)要求指针在画布内、无拖拽、处于 SELECT 工具且有选中对象、且不在文本/节点/钢笔编辑状态;此时按住 Alt 会以shallow/deep两种深度调用editor.setMeasurementMode,驱动画布内测量反馈与光标覆盖。

七、真实用法:EditorCanvas 外壳组件

该 API 的实际调用者正是主应用中的 src/components/EditorCanvas.vue。其调用方式(EditorCanvas.vue)是理解各参数实战含义的最佳示例:

const { cursorOverride, canvasLabelEdit, updateCanvasLabelEdit, commitCanvasLabelEdit, cancelCanvasLabelEdit, autoLayoutPaddingEdit, updateAutoLayoutPaddingEdit, commitAutoLayoutPaddingEdit, cancelAutoLayoutPaddingEdit, cleanupInteractions } = useCanvasInput( canvasRef, store, hitTestSectionTitle, hitTestComponentLabel, hitTestFrameTitle, updatePaneCursor, activatePane, () => isActivePane.value )

要点:

  • 三个命中测试函数(hitTestSectionTitle等)来自编辑器外壳自身的场景查询逻辑,把“哪里是 Section 标题”这类自定义判定交给调用方;
  • updatePaneCursor作为onCursorMove,把指针画布坐标同步给面板层;
  • activatePane作为onActivate,保证点击画布时面板被激活;
  • isEnabled返回() => isActivePane.value,实现多面板(多画布)共存时只有活动面板响应输入;
  • 外壳在面板失活(watch(isActivePane))与组件卸载(onUnmounted)时调用cleanupInteractions()(EditorCanvas.vue),与useTextEdit(canvasRef, store, { isEnabled: () => isActivePane.value })和useCanvasDrop一起构成完整画布外壳。

返回的cursorOverride、autoLayoutPaddingEdit等还驱动着覆盖层:例如内边距编辑器的锚点坐标由paddingEditorAnchor计算(EditorCanvas.vue),再通过useCanvasVirtualReference定位浮层,实现拖拽内边距时实时显示数值编辑器的效果。

八、相关 API 与进一步阅读

useCanvasInput通常与以下 API 配套使用:

  • useCanvas:负责 CanvasKit 加载、surface 创建/重建、尺寸观察、像素比调整与刷新请求,是渲染侧的对偶 composable;
  • useTextEdit:负责 textarea 回退的文本输入、IME 组合、光标闪烁与样式快捷键,与useCanvasInput的文本编辑入口互补;
  • useEditor/provideEditor:从 Vue 注入上下文取得Editor实例(导出于 packages/vue/src/index.ts)。

若需更完整的接入方式,可参考 EditorCanvas.vue 与 SDK 文档目录 packages/docs/fr/programmable/sdk;底层坐标换算与命中测试实现在 packages/vue/src/shared/input/geometry.ts 与 packages/vue/src/canvas/pointer/use.ts。官方还提供了德语、西班牙语、意大利语、波兰语等多语言版本的同主题文档(如 packages/docs/de/programmable/sdk/api/composables/use-canvas-input.md),供交叉参考。

总结

useCanvasInput是 OpenPencil Vue SDK 中画布交互的总调度器:它以统一的DragState状态机覆盖移动、缩放、旋转、平移、框选、绘图、钢笔、参考线与矢量/文本编辑,通过screenToCanvas完成坐标换算,借助 Pointer Capture 与全局事件监听保证交互完整性,并通过回调与isEnabled机制适配多面板编辑器外壳。结合 EditorCanvas.vue 的实战用法,你可以快速在自己的 Vue 组件中搭建出与 OpenPencil 主应用一致的画布交互体验。

  • 前端
  • 桌面应用
  • AI 应用
  • MCP 服务

【免费下载链接】open-pencil

AI-native design editor. Open-source Figma alternative.

项目地址:https://gitcode.com/gh_mirrors/op/open-pencil
点击查看免费下载
上一篇:Python模糊字符串匹配神器TheFuzz:10分钟快速入门指南
下一篇:Ruby爬虫框架Wombat:用优雅DSL轻松提取结构化数据

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 3:03:46

YOLOv3实战:训练行人自行车机动车检测模型的完整流程与避坑指南

简介&#xff1a;这份课程作业以YOLOv3为目标检测骨干网络&#xff0c;面向计算机视觉初学者或需要完成类似课程设计的学生&#xff0c;提供可识别行人、自行车与机动车的完整实现、可视化结果与配套数据集。压缩包共12个文件&#xff0c;核心为3个Python脚本&#xff08;模型构…

作者头像 李华