open-pencil useCanvas 详解:@open-pencil/vue 中 CanvasKit 渲染器与 canvas 元素的连接机制
前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载本文围绕 open-pencilAI 原生开源设计编辑器、Figma 替代方案Vue SDK 中的useCanvas()composable 展开讲解它如何将 CanvasKitSkia 的 WebAssembly 实现渲染器接入一个canvas元素覆盖 CanvasKit 初始化、Surface 创建、渲染调度、Resize 处理与 Rulers 等职责的完整机制。读完后你可以在自定义 Vue 应用中正确嵌入 open-pencil 画布、配置其可选参数如showRulers、preserveDrawingBuffer、onReady并理解其底层的 WebGL Surface 生命周期与 rAF 渲染循环以便做嵌入预览、截图导出或性能层面的定制。useCanvas 的职责useCanvas()是open-pencil/vue包导出的 composable作用是将一个 Editor 实例连接到真实的canvas元素。它负责CanvasKit 初始化Surface 创建渲染调度render schedulingResize 处理可选的 Rulers标尺Renderer 初始化完成后的回调。从源码结构看这些职责被拆分到 packages/vue/src/canvas/surface/use.ts 及其配套模块中职责实现位置CanvasKit 加载与初始化packages/vue/src/canvas/surface/kit-loader.tsSurface / GL 上下文创建packages/vue/src/canvas/surface/lifecycle.ts、packages/vue/src/canvas/surface/gl-surface.ts渲染循环与事件订阅packages/vue/src/canvas/surface/render-loop.tsResize 监听packages/vue/src/canvas/surface/resize-observer.tsRulers 可见性逻辑packages/vue/src/canvas/surface/overlays.tsuseCanvas在包入口 packages/vue/src/index.ts 中导出export { useCanvas } from #vue/canvas/surface/use与文档中最小类型声明不同的是useCanvas的返回值并非void源码中它返回一组渲染与命中测试工具return { render: surface.markDirty, // 标记脏帧并调度渲染 renderNow: surface.renderNow, // 立即同步渲染 hitTestSectionTitle, // 渲染器支撑的标题命中测试 hitTestComponentLabel, hitTestFrameTitle }这些命中测试方法hitTestSectionTitle、hitTestComponentLabel、hitTestFrameTitle委托给底层SkiaRenderer的对应方法供更高层的画布交互代码使用——也就是说图层标签、组件标签这类绘制在渲染器中的文字其点击判定是走 Skia 渲染器路径的。基本用法import { ref } from vue import { useCanvas, useEditor } from open-pencil/vue const canvasRef refHTMLCanvasElement | null(null) const editor useEditor() useCanvas(canvasRef, editor)完整示例Vue SFCscript setup langts import { ref } from vue import { useCanvas, useEditor } from open-pencil/vue const canvasRef refHTMLCanvasElement | null(null) const editor useEditor() useCanvas(canvasRef, editor, { showRulers: true, onReady: () { console.log(Renderer ready) }, }) /script template canvas refcanvasRef classsize-full / /template最小类型声明如下interface UseCanvasOptions { showRulers?: boolean preserveDrawingBuffer?: boolean onReady?: () void } function useCanvas( canvasRef: RefHTMLCanvasElement | null, editor: Editor, options?: UseCanvasOptions, ): void其中editor参数类型为open-pencil/core/editor导出的EditorcanvasRef必须是一个指向canvasDOM 节点的Ref。完整的 UseCanvasOptions文档中列出的三个选项是最常用子集源码 packages/vue/src/canvas/surface/types.ts 中的UseCanvasOptions实际支持更多高级参数选项类型说明showRulersboolean?强制打开/关闭该画布的标尺。省略时回退到视口逻辑见下文。preserveDrawingBufferboolean?提交帧之后保留 drawing buffer适合截图或像素读回pixel-readback工作流文档注明可能因浏览器与 GPU 后端不同而增加内存占用。onReady() void渲染 Surface 就绪后回调一次。layerfull \| scene \| overlays选择该画布拥有的渲染层。getRenderState() EditorState提供该画布实际渲染的视图状态默认editor.state。多个画布可共享一份文档图、历史与事件总线但各自使用独立的视图状态。shouldSuspendRender() boolean返回true时挂起实际渲染但仍保留脏标记并继续调度。onPresented(versions) void每帧提交后报告本次渲染的renderVersion/sceneVersion。sceneRendererretained \| tiled为该 Surface 启用实验性的 tiled分块场景渲染器。onPresentation(colorSpace \| null) void报告画布实际呈现的色域含回退情况无法配置 Surface 时为null。onViewportResize(width, height) void在创建与 resize 之后接收画布的 CSS 视口尺寸。初始化流程从 CanvasKit 加载到 onReadyuseCanvas挂载后并不会立刻画出任何东西它通过 kit-loader.ts 中的useCanvasKitLoader在onMounted中启动一条异步初始化链调用getCanvasKit()来自open-pencil/core/canvaskit异步加载 CanvasKit WASM 模块等待一个requestAnimationFrame确保 canvas 元素已具备布局尺寸执行createSurface(canvas)创建 WebGL Surface 与SkiaRendererloadFonts()加载字体失败不阻断流程执行首次renderNow()最后调用onReady?.()。每一步之间都检查lifecycle.destroyed组件在初始化中途被卸载时不会继续操作已销毁的资源。onReady因此是渲染管线完全可用的信号适合在此处放开 UI 加载态或启动依赖渲染器的功能。Surface 创建与 WebGL 上下文Surface 的创建逻辑集中在 lifecycle.ts 的createSurface和 gl-surface.ts 的makeGLSurface中流程如下先销毁旧资源若已存在 renderer 与 GL context先editor.removeCanvasRenderer()、destroy()并delete()旧上下文避免残留 GPU 资源。尺寸换算sizeCanvas()以devicePixelRatio缩放canvas.width/heightcanvas.width clientWidth * dpr并通过onViewportResize回调或editor.setViewportSize(width, height)把 CSS 视口尺寸通知给 editor。获取 GL 上下文调用ck.GetWebGLContext(canvas, glAttrs)。这里正是preserveDrawingBuffer选项的作用点const glAttrs options?.preserveDrawingBuffer ? { preserveDrawingBuffer: 1 } : undefined const handle context ? null : ck.GetWebGLContext(canvas, glAttrs)即选项最终映射为 WebGL context 属性preserveDrawingBuffer: 1这也是为什么它可能增加内存占用——浏览器不会在合成后自动清空 back buffer。色域配置configurePresentation()根据文档色域editor.graph.documentColorSpace与设备广色域支持能力决定呈现色域随后用ck.MakeOnScreenGLSurface()以DISPLAY_P3或SRGB色彩空间创建 Surface。注册渲染器new SkiaRenderer(ck, surface, glCtx)并通过editor.setCanvasKit(ck, renderer)挂接到 editor成功后在 DOM 上打标记canvas.dataset.ready 1。两个值得注意的边界处理若 WebGL Surface 创建失败会写入canvas.dataset.surfaceError webgl并直接返回——宿主页面可以通过这个 data 属性检测渲染失败文档色域为display-p3时 Surface 必须是 P3反之亦然。由于文档在挂载之后才到达lifecycle.ts订阅了graph:replaced与document:color-space-changed两个 editor 事件一旦所需色域与当前 Surface 不匹配就整体重建 Surface 并重新加载字体reloadFonts: true。渲染调度基于 rAF 的脏帧循环渲染调度由 render-loop.ts 中的createCanvasRenderLoop实现核心设计是事件驱动 每帧合并 版本去重事件订阅监听 editor 事件render:requested标脏并调度、viewport:changed调度、repaint:requested标脏并调度当layer ! scene时还监听selection:changed因为纯场景层画布不需要跟随选中态重绘。共享调度器每个Editor通过WeakMap缓存一个共享的 rAF 调度器同一帧内多个回调会合并到一次requestAnimationFrameflush 中执行。版本去重每次渲染前比较renderVersion、sceneVersion与selectedIds三者均未变化且非脏帧时直接跳过避免无意义的 GPU 提交。挂起渲染shouldSuspendRender()返回true时例如拖拽期间用轻量预览代替完整渲染循环只保留脏标记并继续调度待条件解除后补渲染。实际渲染入口renderNow()lifecycle.ts调用renderer.renderFromEditorState(...)参数依次为渲染状态默认editor.state可用getRenderState覆盖、文档图、文本编辑器、画布 CSS 尺寸、标尺可见性、layer默认full以及是否处于交互编辑。若该画布是scene层且启用了 tiled 场景渲染器且尚有分块未完成markRendered之后会再次markDirty()继续下一轮直到分块全部收敛。Resize 处理Resize 由 resize-observer.ts 基于vueuse/core的useResizeObserver实现并用requestAnimationFrame做节流——多次连续 resize 只在下帧处理一次useResizeObserver(canvasRef, () { const canvas canvasRef.value if (!canvas || !getCanvasKitValue() || resizeRaf) return resizeRaf requestAnimationFrame(() { resizeRaf 0 resizeCanvas(canvas) }) })resizeCanvas()lifecycle.ts在已有 renderer 时不会整个重建而是重新sizeCanvasmakeGLSurface后调用renderer.replaceSurface(surface)代价更低只有当替换后的 Surface 创建失败时才会回退到完整重建Falling back to full surface recreation after resize并顺带重新加载字体因为销毁重建过程中模块级 font provider 已被清空。Rulers标尺的显隐逻辑标尺可见性由 overlays.ts 的createRulerVisibility决定return function shouldShowRulers() { if (options?.showRulers false) return false return !isMobile.value }从源码结构看其语义是showRulers false无条件隐藏其余情况回退到视口逻辑——当useViewportKind()判定为移动端mobile时自动隐藏。类型注释中提到的viewport and URL-param logic表明完整产品中还存在 URL 参数级别的控制而 SDK 层暴露给嵌入方的开关就是showRulers。每次renderNow()都会实时读取shouldShowRulers()因此标尺显隐可以随视口变化即时生效无需重建 Surface。两个典型用法继承自原文档嵌入预览中隐藏标尺useCanvas(canvasRef, editor, { showRulers: false, })为截图保留 drawing bufferuseCanvas(canvasRef, editor, { preserveDrawingBuffer: true, })生命周期与资源清理useCanvas的清理逻辑绑定 Vue 作用域onScopeDispose组件卸载时置位lifecycle.destroyed、cancelResize()取消挂起的 resize 帧、surface.destroy()内依次停止事件订阅、暂停渲染循环、从 editor 移除渲染器、销毁 renderer 与 GL 上下文见 lifecycle.ts 的destroy与useCanvasSurfaceLifecycle。整个 composable 不管理文档的打开/保存也不持有文件状态——这与文档它管理的是活跃 Canvas而不是打开或保存文件的说明一致。组合使用与适用边界useCanvas()集成渲染器面向浏览器环境设计它管理活跃的 Canvas不负责文件的打开或保存对于 Pointer 交互通常与useCanvasInput()组合使用后者负责把指针事件翻译为编辑操作。参考文档useCanvas、useEditor、useCanvasInput、useTextEdit、Composables 总览源码useCanvas 入口、Surface 生命周期、GL Surface 创建、渲染循环、CanvasKit 加载、Resize 监听、标尺与命中测试、选项类型定义赞分享前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载相关推荐open-pencil open-pencil/vue Composables用 provideEditor、useEditor 与 useCanvas 构建自定义设计编辑器界面open pencil open pencil/vue Composables用 provideEditor、useEditor 与 useCanvas 构前端桌面应用AI 应用MCP 服务open-pencil open-pencil/vue 中的 ToolbarRoot为设计编辑器构建 Headless 工具栏原语open pencil open pencil/vue 中的 ToolbarRoot为设计编辑器构建 Headless 工具栏原语 ToolbarRoot前端桌面应用AI 应用MCP 服务OpenPencil open-pencil/vue CanvasSurface 实战指南应用自持布局下的 SDK 画布渲染OpenPencil open pencil/vue CanvasSurface 实战指南应用自持布局下的 SDK 画布渲染 本文以官方 SDK 文档中的前端桌面应用AI 应用MCP 服务上一篇Waymo开放数据集标注规范详解3D与2D目标标注指南下一篇ConsoleZWindows终端增强工具全面解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考