如何用 ExcalidrawElementSkeleton 与 convertToExcalidrawElements 以代码方式创建 Excalidraw 元素?
2026/9/9 19:12:55 网站建设 项目流程

如何用 ExcalidrawElementSkeleton 与 convertToExcalidrawElements 以代码方式创建 Excalidraw 元素?

【免费下载链接】excalidrawVirtual whiteboard for sketching hand-drawn like diagrams项目地址: https://gitcode.com/GitHub_Trending/ex/excalidraw

如果你的项目把 Excalidraw 作为嵌入式白板组件使用,并需要在代码中动态生成画布内容——例如根据接口数据自动绘制流程图、在用户点击按钮时往画布追加一组形状和箭头——那么手写完整的ExcalidrawElement对象会非常繁琐:每个元素都有idversionseedboundElements等几十个字段。Excalidraw 提供了一条简化路径:用只包含最少属性的ExcalidrawElementSkeleton描述元素,再通过convertToExcalidrawElements转换成完整元素,交给initialDataupdateScene渲染到画布上。

需要注意的是,Skeleton API 目前仍处于beta 状态,官方文档明确说明它在转为 stable 之前可能变化(参见 Skeleton API 文档)。下文所有用法均以该文档及其配套实现 transform.ts 为准。

准备条件:安装与导入

按 安装文档,用 npm 或 yarn 安装包:

npm install react react-dom @excalidraw/excalidraw # 或 yarn add react react-dom @excalidraw/excalidraw

使用模块打包器(如 Webpack)时,按 ES6 模块导入即可(参见 集成文档):

import { Excalidraw } from "@excalidraw/excalidraw"; import { convertToExcalidrawElements } from "@excalidraw/excalidraw";

如果项目使用 Next.js,Excalidraw 不支持服务端渲染,必须只在客户端渲染。集成文档给出的做法是用next/dynamic动态导入并设置ssr: false;当需要同时导入convertToExcalidrawElements这类工具函数(而非Excalidraw组件本身)时,动态导入命名导出的方式不生效,文档建议写一个 wrapper 组件再整体动态导入,app router 下还需在文件顶部加"use client"指令:

"use client"; import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw"; import "@excalidraw/excalidraw/index.css";

convertToExcalidrawElements 的签名与参数

函数签名(来自 Skeleton 文档):

convertToExcalidrawElements( elements: ExcalidrawElementSkeleton, opts?: { regenerateIds: boolean } ): ExcalidrawElement[]

两个参数需要注意:

参数类型默认值说明
elementsExcalidrawElementSkeleton待转换的元素 Skeleton。文档中的全部示例都以数组形式传入
opts.regenerateIdsbooleantrue默认会为所有元素重新生成id,即使你传了id也会覆盖。如果希望保留自己指定的id,需显式传{ regenerateIds: false }

实现位于 transform.ts,当opts.regenerateIds !== false时对每个元素执行Object.assign(element, { id: randomId() }),这与文档描述的默认行为一致。

关键前提:Skeleton 本身不能直接渲染。文档明确写道:convertToExcalidrawElements必须在把元素传给initialDataupdateScene等 API 之前调用,转换得到的ExcalidrawElement[]才能被画布渲染。

各类型 Skeleton 的必填字段与写法

基本图形:rectangle、ellipse、diamond

只需typexy三个必填属性,其余样式属性可选。不传width/height时使用默认尺寸,可附加backgroundColorstrokeColorstrokeStylefillStylestrokeWidth等属性装饰形状(文档示例值,可整体替换后运行):

convertToExcalidrawElements([ { type: "rectangle", x: 50, y: 250, width: 200, height: 100, backgroundColor: "#c0eb75", strokeWidth: 2, }, { type: "ellipse", x: 300, y: 250, width: 200, height: 100, backgroundColor: "#ffc9c9", strokeStyle: "dotted", fillStyle: "solid", strokeWidth: 2, }, { type: "diamond", x: 550, y: 250, width: 200, height: 100, backgroundColor: "#a5d8ff", strokeColor: "#1971c2", strokeStyle: "dashed", fillStyle: "cross-hatch", strokeWidth: 2, }, ]);

文本元素

typexytext四项必填,fontSizestrokeColor等可选:

convertToExcalidrawElements([ { type: "text", x: 100, y: 100, text: "HELLO WORLD!", }, { type: "text", x: 100, y: 150, text: "STYLED HELLO WORLD!", fontSize: 20, strokeColor: "#5f3dc4", }, ]);

线段与箭头

type"line""arrow")、xy必填。可附加startArrowheadendArrowheadstrokeColorstrokeWidthstrokeStyle等:

convertToExcalidrawElements([ { type: "arrow", x: 450, y: 20, startArrowhead: "circle", endArrowhead: "triangle", strokeColor: "#1971c2", strokeWidth: 2, }, { type: "line", x: 450, y: 60, strokeColor: "#2f9e44", strokeWidth: 2, strokeStyle: "dotted", }, ]);

文本容器(带 label 的形状)

typexy之外,必须提供label属性,且label.text必填;label内的strokeColorfontSizetextAlignverticalAlign等可选。文档说明:如果不提供容器尺寸,会根据 label 的尺寸计算容器大小。

convertToExcalidrawElements([ { type: "rectangle", x: 300, y: 290, label: { text: "RECTANGLE TEXT CONTAINER", }, }, { type: "ellipse", x: 500, y: 100, label: { text: "ELLIPSE\n TEXT CONTAINER", }, }, ]);

带标签的箭头与箭头绑定

箭头同样支持label。更进一步,通过start/end属性可以把箭头绑定到形状或文本上:startend中传typeid之一即可。当start/end没有给出坐标时,会按箭头自身位置计算绑定元素的位置。

文档中的箭头绑定示例(整体可运行,值来自文档):

convertToExcalidrawElements([ { type: "ellipse", id: "ellipse-1", strokeColor: "#66a80f", x: 390, y: 356, width: 150, height: 150, backgroundColor: "#d8f5a2", }, { type: "diamond", id: "diamond-1", strokeColor: "#9c36b5", width: 100, x: -30, y: 380, }, { type: "arrow", x: 100, y: 440, width: 295, height: 35, strokeColor: "#1864ab", start: { type: "rectangle", width: 150, height: 150, }, end: { id: "ellipse-1", }, }, { type: "arrow", x: 60, y: 420, width: 330, strokeColor: "#e67700", start: { id: "diamond-1", }, end: { id: "ellipse-1", }, }, ]);

这里的要点:多条箭头引用同一个已有元素时用id绑定。因为默认regenerateIds: true,你写的id会被重新生成,实现内部会维护旧 id 到新 id 的映射(oldToNewElementIdMap),自动把start/end里的旧 id 改写为新生成的 id,所以按文档这样写即可直接运行;只有在你显式传{ regenerateIds: false }复用既有元素时,才需要保证id与元素自身一致。

创建 frame

frame 的必填属性是type: "frame"children(成员元素 id 的数组),name可选:

convertToExcalidrawElements([ { "type": "rectangle", "x": 10, "y": 10, "strokeWidth": 2, "id": "1" }, { "type": "diamond", "x": 120, "y": 20, "backgroundColor": "#fff3bf", "strokeWidth": 2, "label": { "text": "HELLO EXCALIDRAW", "strokeColor": "#099268", "fontSize": 30 }, "id": "2" }, { "type": "frame", "children": ["1", "2"], "name": "My frame" } ]);

从 实现代码 可以看到:frame 会在所有子元素处理完之后统一处理,children引用的 id 会被解析为实际元素,frame 的坐标和尺寸默认根据子元素的外包边界自动计算(含 padding)。

把转换结果交给 Excalidraw 渲染

路径一:挂载时通过 initialData 加载

convertToExcalidrawElements的返回值直接放进 initialData 的elements字段。initialData支持elementsappStatescrollToContent等字段;scrollToContent: true会在挂载后自动滚动到元素使其居中。Skeleton 文档给出的完整示例:

function App() { const elements = convertToExcalidrawElements([ { type: "rectangle", x: 100, y: 250, }, { type: "ellipse", x: 250, y: 250, }, { type: "diamond", x: 380, y: 250, }, ]); return ( <div style={{ height: "500px" }}> <Excalidraw initialData={{ elements, appState: { zenModeEnabled: true, viewBackgroundColor: "#a5d8ff" }, scrollToContent: true, }} /> </div> ); }

注意容器要有非零尺寸——Excalidraw 占满外层容器的宽高(见 安装文档 中 “Dimensions of Excalidraw” 一节)。

路径二:运行中通过 excalidrawAPI.updateScene 更新

如果画布已挂载、需要动态更新场景,先通过excalidrawAPI回调拿到 API 并存入 state(参见 Excalidraw API 文档):

function App() { const [excalidrawAPI, setExcalidrawAPI] = useState(null); return ( <div style={{ height: "500px" }}> <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} /> </div> ); }

之后调用updateScene,参数结构含elementsappStatecaptureUpdate等字段。用 Skeleton 生成元素时,同样是先转换再传入:

const sceneData = { elements: convertToExcalidrawElements([ { type: "rectangle", x: 100, y: 100 }, { type: "text", x: 120, y: 120, text: "NEW ELEMENT" }, ]), appState: { viewBackgroundColor: "#edf2ff" }, }; excalidrawAPI.updateScene(sceneData);

文档提示Ref支持已在 v0.17.0 移除,必须改用excalidrawAPI方式访问 API。

验证结果与常见报错

文档给出的验证方式是渲染结果:把转换后的元素传给initialDataupdateScene后,画布上应出现对应的形状、文本、箭头绑定和 frame。仓库内也提供了对应的单元覆盖,transform.test.ts 对convertToExcalidrawElements的各类输入做了行为断言,可作为转换行为的参照。

排查时可以关注实现里明确存在的几类控制台报错(transform.ts):

  • No element for start binding with id {id} found/No element for end binding with id {id} found:箭头start/end中通过id绑定时,找不到对应元素——检查id是否拼错、是否在传入的 Skeleton 数组中定义过;
  • Duplicate id found for {id}:同一次转换中出现重复id(在regenerateIds: false时更容易触发);
  • Element with {id} wasn't mapped correctly/Excalidraw element with id {id} doesn't exist:frame 的children引用了不存在或未正确映射的元素 id;
  • 文本绑定缺少text时会报No text found for start/end binding text element

边界与限制

  • 该 API 为 beta,签名与行为在 stable 之前可能调整,升级@excalidraw/excalidraw后建议回归验证一次转换结果。
  • regenerateIds默认true:不显式设false时,自己写的id会被覆盖,跨渲染周期复用同一批元素时需注意这一点。
  • 箭头绑定中,start/end支持以type内联声明被绑定元素(此时该元素由转换过程生成),也支持以id引用数组内已有元素;文档示例覆盖rectangleellipsediamondtext这几类可绑定类型。

完成以上步骤后,你就可以只写最少属性在代码中生成 Excalidraw 元素,并通过initialDataupdateScene验证它们出现在画布上。

【免费下载链接】excalidrawVirtual whiteboard for sketching hand-drawn like diagrams项目地址: https://gitcode.com/GitHub_Trending/ex/excalidraw

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询