- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
导读:OpenPencil 不仅是开箱即用的 AI 原生设计编辑器,更是一套可编程 SDK。本文聚焦@open-pencil/vue的核心用法——通过provideEditor、CanvasRoot、ToolbarRoot、PageListRoot、LayerTreeRoot等无样式(headless)组件与 composable,从零搭建一套完全由应用自己决定布局、样式与交互的自定义编辑器界面。读完本文,你将掌握三层架构的职责边界、编辑器外壳的推荐布局结构,以及如何用一个最小完整示例把画布、工具栏、页面列表与属性面板拼装成自己的产品。
三层架构:core 负责逻辑,vue 负责连接,应用负责外观
OpenPencil 的 Vue 应用天然分为三个层次,理解这三层边界是构建自定义编辑器外壳的前提:
@open-pencil/core:创建编辑器实例。它是与框架无关的编辑模型核心,负责场景图、选择状态、工具状态、Undo 历史等编辑逻辑;@open-pencil/vue:把 core 连接到 Vue。它提供 composables 与无预设外观(headless)的组件,但不规定任何视觉呈现;- 应用层:由你决定排列(layout)、样式(styling)与产品特定的行为(如路由、文件打开/保存、通知)。
这套分层的设计原则在 SDK 架构文档中有明确表述:@open-pencil/vue只是补充了 Vue 依赖注入、响应式 composables、无样式结构组件以及画布与输入处理,编辑器模型始终保留在 core 中(见 SDK-Architektur)。SDK 架构中有一条判断归属的“经验法则”:如果一段代码脱离本应用的样式后仍能在另一个基于 OpenPencil 的编辑器里复用,那它大概率应该放进@open-pencil/vue;反之,属于本产品的布局、品牌与业务逻辑则留在应用层。
可能的应用场景
成品 OpenPencil 应用只是 SDK 能构建的一种界面。借助该 SDK,你可以实现:
- 嵌入到其他产品中的编辑器(embedded editor);
- 内部资产工具(internal asset tool);
- 模板编辑器(template editor);
- 标注 UI(annotation UI);
- 面向特定工作流的 AI 辅助编辑器(specialized AI-assisted editor)。
这些场景的共同点是不需要重新发明编辑内核,只需要围绕 SDK 提供的集成层重写界面。
推荐的编辑器外壳结构
一个典型的外壳(shell)遵循以下骨架:
- 在组件树高处调用
provideEditor(),把编辑器实例注入整个子树; - 画布(Canvas)放在中央;
- Pages 与 Layers 放在侧边面板;
- 属性(Properties)放在对侧面板;
- 菜单与工具栏通过 composables 驱动,而不是套用固定组件。
在 Vue 中,provideEditor()的实现本质是依赖注入:它在 packages/vue/src/editor/context/index.ts 中把编辑器实例以Symbol('open-pencil-editor')(即EDITOR_KEY)为键provide给子树,之后的任意 composable 都能通过useEditor()读到同一实例。若在未提供编辑器的子树中调用useEditor(),会直接抛出错误——这正是外壳必须“先 provide、后使用”的原因。
最小完整示例:一个三栏编辑器外壳
下面这个示例完整展示了上述结构。它用 Tailwind 网格把界面划分为顶部工具栏、左侧页面/图层面板、中央画布和右侧属性面板,所有交互都由 SDK 的 slot props 驱动:
<script setup lang="ts"> import { createEditor } from '@open-pencil/core/editor' import { provideEditor, CanvasRoot, CanvasSurface, ToolbarRoot, PageListRoot, LayerTreeRoot, } from '@open-pencil/vue' // 创建编辑器实例(宽 1440 × 高 900),并注入整个组件子树 const editor = createEditor({ width: 1440, height: 900 }) provideEditor(editor) </script> <template> <div class="grid h-screen grid-cols-[240px_1fr_320px] grid-rows-[48px_1fr]"> <!-- 顶部工具栏:遍历 tools,渲染按钮,用 activeTool 高亮当前工具 --> <ToolbarRoot v-slot="{ tools, activeTool, setTool }"> <header class="col-span-3 flex items-center gap-2 border-b px-3"> <button v-for="tool in tools" :key="tool.id" :data-active="activeTool === tool.id" @click="setTool(tool.id)" > {{ tool.label }} </button> </header> </ToolbarRoot> <!-- 左侧面板:页面列表 --> <aside class="border-r"> <PageListRoot v-slot="{ pages, currentPageId, switchPage }"> <nav> <button v-for="page in pages" :key="page.id" :data-active="page.id === currentPageId" @click="switchPage(page.id)" > {{ page.name }} </button> </nav> </PageListRoot> </aside> <!-- 中央画布 --> <main> <CanvasRoot> <CanvasSurface class="size-full" /> </CanvasRoot> </main> <!-- 右侧面板:属性面板(可由属性指南中的 composables 填充) --> <aside class="border-l"> Properties-Panel </aside> </div> </template>注意示例中CanvasSurface上的class="size-full":CanvasSurface 是一个透传属性的<canvas>元素(其实现会v-bind="$attrs",见 CanvasSurface.vue),因此画布的尺寸、定位、圆角等完全由应用层的 CSS 决定。上例再配合LayerTreeRoot放入左侧面板(位于页面列表下方),即可组成完整的导航侧栏;展开图层树的写法参见 导航面板指南。
各部件职责与源码级实现
provideEditor / useEditor:整个外壳的依赖注入入口
provideEditor(editor)应在组件树最高层(如根组件或专门的 Shell 组件)调用一次。它把Editor实例放入 Vue 的注入上下文,后续所有 composables(useCanvas、usePageList、useToolbar、useEditorCommands等)都通过useEditor()取回该实例(见 provideEditor 文档)。
一个值得注意的兼容性细节:当前 SDK 直接使用provideEditor()/useEditor();旧版示例与个别错误信息中出现的OpenPencilProvider组件并不属于当前公开 API,新代码请一律使用 composable 形式。
CanvasRoot / CanvasSurface:画布集成的两端
- CanvasRoot是一个无样式上下文组件:它内部通过
useCanvas()完成 CanvasKit 初始化、Surface 创建、渲染调度、resize 处理与可选标尺,并把canvasRef、ready、renderNow等能力provide给子树(见 CanvasRoot.vue 与 CanvasRoot 文档); - CanvasSurface负责渲染实际的
<canvas>元素,并把自身 DOM 节点同步给CanvasRoot提供的 ref(见 CanvasSurface.vue 与 CanvasSurface 文档)。
如果想跳过组件层直接管理画布,也可以使用底层 composableuseCanvas(canvasRef, editor, options)。它支持以下选项(类型定义见 useCanvas 文档):
| 选项 | 类型 | 说明 |
|---|---|---|
showRulers | boolean | 是否显示画布标尺;嵌入预览时通常设为false |
preserveDrawingBuffer | boolean | 保留 drawing buffer,便于截图 |
onReady | () => void | 渲染器初始化完成后的回调 |
直接用法示例:
import { ref } from 'vue' import { useCanvas, useEditor } from '@open-pencil/vue' const canvasRef = ref<HTMLCanvasElement | null>(null) const editor = useEditor() useCanvas(canvasRef, editor, { showRulers: false, // 嵌入预览时隐藏标尺 preserveDrawingBuffer: true, // 为截图保留绘制缓冲 onReady: () => console.log('Renderer ready'), })从源码看,useCanvas是渲染器生命周期(创建、resize 重建 Surface、调度渲染)与基于渲染器的命中测试(标题、组件标签、帧标题)的归属处(见 packages/vue/src/canvas/surface/use.ts)。它只负责管理活动画布,并不负责文件的打开与保存;指针交互通常还要与useCanvasInput配合。
ToolbarRoot:工具栏数据与切换动作
ToolbarRoot通过 slot 暴露可用工具列表、当前活动工具与切换动作(tools、activeTool、setTool)。从 ToolbarRoot.vue 的实现可以看到:
- 工具列表默认取 core 导出的
EDITOR_TOOLS,也支持通过 prop 传入自定义的EditorToolDef[]; activeTool直接派生自editor.state.activeTool;setTool(tool)内部调用editor.setTool(tool),切换后关闭展开的 flyout;- 支持 flyout 子工具:
flyoutSelections记录每个工具组在当前活动工具下应展开的选择,toggleFlyout/closeFlyout控制展开状态。
因此应用层只需提供按钮与图标,高亮、切换、flyout 选择等编辑语义全部由 SDK 管理。
PageListRoot / usePageList:页面导航状态与动作
PageListRoot把usePageList()的状态(pages、currentPageId)与动作(switchPage、addPage、renamePage、deletePage、movePage)打包进 slot。查看 PageListRoot.vue 可发现两个实用细节:
- divider 识别:默认用正则
/^[-–—*\s]+$/判断“纯分隔线页面”(如名为---的页),可通过dividerPatternprop 自定义; - 语义事件:
add、switch、rename、delete、move都会在内部动作之后通过emit冒泡,方便上层做统计或联动。
不想用组件时,直接在任意组件里const { pages, currentPageId, switchPage, addPage } = usePageList()即可(见 usePageList 文档)。
LayerTreeRoot:图层树结构与行为
LayerTreeRoot由 SDK 管理树的展开/折叠、选中、拖拽重排与缩进,应用层负责把items渲染成自己的树形控件。从 LayerTreeRoot.vue 与 context.ts 的源码看,它通过provideLayerTree向下注入items、expanded、选中状态与动作,并提供indentPerLevel(默认 16px)等结构参数;此外还整合了useLayerDrag处理reorder-above/reorder-below/make-child三种拖拽投放语义。典型用法是把items、selectedIds、getKey、getChildren桥接给自定义的TreeView组件:
<LayerTreeRoot v-slot="{ items, selectedIds, select, toggleExpand, getKey, getChildren }"> <TreeView :items="items" :selected-ids="selectedIds" :get-key="getKey" :get-children="getChildren" @select="select" @toggle-expand="toggleExpand" /> </LayerTreeRoot>职责分工:SDK 与应用各管什么
把整份自定义外壳的职责理清,是避免重复造轮子或把业务逻辑塞进组件的关键:
SDK(@open-pencil/vue)负责:
- 与编辑器内核的集成(依赖注入、画布渲染绑定);
- 可复用、与外观无关的逻辑(选择、页面、图层、属性编辑状态);
- 可复用的 UI 结构,但不规定任何视觉;
- 画布渲染与输入处理。
应用负责:
- 全部样式与品牌设计;
- 页面整体布局与路由;
- 文件打开、保存等文件操作;
- 通知、菜单与应用特定行为。
composables 是数据通道:它们为菜单和面板提供所需数据,却不强制任何包裹组件。需要从选择状态计算属性值并执行更新动作时,用 composable(如usePosition、useLayout、useAppearance、useTypography、useFillControls等);需要协调重复出现的列表/树形结构时,才使用PropertyListRoot、LayerTreeRoot这类结构组件。属性面板的完整搭建方法(含usePosition位置尺寸示例与useFillControls填充列表示例)见 属性面板指南。
另外,SDK 架构文档还给出两条 API 设计约定,对应用层消费方式同样重要:不要通过 slot 把整个上下文一股脑抛给消费者,只暴露需要的 props,或直接使用 composable;受控组件(如PropertyListRoot)通过语义化事件上报动作,选择与 Undo 的衔接应放在 adapter 或控制型 composable 中,而非组件内部。
延伸阅读
- SDK 架构:包结构、公开 API 边界与设计原则;
- 导航面板指南:PageListRoot 与 LayerTreeRoot 的完整导航侧栏组合;
- 属性面板指南:composables 与 PropertyListRoot 构建属性面板;
- provideEditor、useCanvas、ToolbarRoot、PageListRoot、LayerTreeRoot:本文涉及组件的完整 API 参考。
- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
相关推荐
5 分钟把网页视频存到本地:猫抓资源嗅探插件完整指南
5 分钟把网页视频存到本地:猫抓资源嗅探插件完整指南 那个你以为只能在线看的视频,其实一直挂在页面的网络请求里。猫抓是一款开源浏览器资源嗅探扩展,把网页里的视频
前端桌面应用AI 应用MCP 服务OpenPencil Vue SDK 的 provideEditor:向组件树注入 Editor 实例的正确姿势
OpenPencil Vue SDK 的 provideEditor:向组件树注入 Editor 实例的正确姿势 provideEditor editor 是
前端桌面应用AI 应用MCP 服务OpenPencil Vue SDK:使用 AppearanceControlsRoot 构建自定义外观控制面板
OpenPencil Vue SDK:使用 AppearanceControlsRoot 构建自定义外观控制面板 本文以 OpenPencil 官方文档中的 A
前端桌面应用AI 应用MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考