关于 excalidraw
excalidraw 是用于创建、编辑和分析 Excalidraw `.excalidraw` 场景文件的技能。小型修改可直接编写 JSON,较大的图表则可通过脚本构建、检查并渲染。
主要功能
- 01创建和编辑 `.excalidraw` 场景文件
- 02以 JSON 手动编写场景
- 03使用 JavaScript 工具链构建图表
- 04将 Mermaid 图表转换为 Excalidraw 场景
- 05检查几何布局并渲染 SVG 预览
适用场景
- 01创建架构图和流程图
- 02编辑或扩展现有场景
- 03根据场景元素概述图表内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ djalmajr/ skills --skill excalidraw安装后在 Claude Code 中输入/excalidraw确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/architecture.example.mjsreferences/element-reference.mdminimal-scene.excalidrawscripts/.gitignoredom-shim.mjsexcalidraw-lib.mjslint-scene.mjspackage.jsonREADME.mdrender.mjssetup.shscripts/vendor/entry.mjs详细介绍
excalidraw
创建和编辑 .excalidraw 文件 — 在无限画布上由 元素(矩形、文本、箭头)组成的扁平场景。根据规模,有两种创作方式:
- 手动(JSON) — 适用于少量方框,或编辑现有场景(重新着色、重命名、重新连接、追加)。下文的元素格式和绑定/分组规则就是你需要了解的内容 — 无论采用哪种方式,都要阅读这些规则,因为程序化方式也会生成这些元素。
- 程序化 — 适用于规模更大或需要长期维护的场景。
scripts/中的工具链使用 Excalidraw 自身的convertToExcalidrawElements(它会处理容易出错之处:相互绑定、容器标签、文本尺寸、z 顺序),用exportToSvg渲染忠实还原的 SVG 预览,并检查几何布局。参见下文的程序化创作。
.excalidraw是 Excalidraw 自有的磁盘 JSON 格式。Mermaid 是独立的工具,但可用作实用的输入:对于标准图形(流程图、时序图、类图、ER 图),可以编写 Mermaid 并将其转换为原生 Excalidraw 场景 — 使用编辑器内置的“Mermaid to Excalidraw”,或使用scripts/中的fromMermaid()。无论采用哪种方式,之后编辑的文件都是 Excalidraw JSON,这正是本技能介绍的内容。
适用场景
- 从头创建
.excalidraw文件(架构图、流程图、方框和箭头图)。 - 编辑现有场景:添加一个分区、替换其中一部分、重新着色/重命名方框、重新连接箭头。
- 阅读场景并对其进行总结或推理(根据元素构建大纲)。
如果有可用的 Excalidraw 编辑器,且用户只是想绘图,优先使用编辑器。本技能用于直接生成/编辑文件 — 小型场景或编辑工作选择手动方式,复杂图表选择程序化方式(下一节)。
程序化创作(预览和检查循环)
对于复杂或需要维护的图表,不要手动放置几十个元素 — 使用 scripts/ 中的 JS 工具链。它封装了 Excalidraw 的官方函数,因此生成结果不会偏离格式:
convertToExcalidrawElements(skeleton)— Excalidraw 自有的编译器。传入高层级图形以及箭头连接的方框,它会返回有效元素,并包含相互绑定、容器标签、测量后的文本、ID 和 z 顺序。这是正确性方面最大的提升 — 下文的易错点将由它处理,而不是由你处理。exportToSvg(scene)— 渲染真实效果(粗糙笔触、字体),因此预览不会像近似渲染器那样失真。
一次性设置(安装无头工具链并打包 @excalidraw/*;需要 node 和 npm,以及用于无头文本测量的 canvas/jsdom):
sh scripts/setup.sh
然后循环执行 — 构建 → 检查 → 渲染 → 查看 → 迭代:
node examples/architecture.example.mjs # build with the Diagram builder
node scripts/lint-scene.mjs /tmp/arch.excalidraw # arrow crossings / label overflow / overlap
node scripts/render.mjs /tmp/arch.excalidraw # -> /tmp/arch.svg (faithful)
rsvg-convert -w 1200 /tmp/arch.svg -o /tmp/arch.png # rasterize, then actually LOOK
查看渲染结果是最重要的一步。 即使 JSON 有效,图表仍可能难以阅读 — 箭头穿过无关方框、标签偏离卡片。检查器能发现机械性问题;路由是否合理则要靠你自己判断。迭代修改构建器代码,而不是 JSON。
Diagram 构建器(scripts/excalidraw-lib.mjs)提供 card()、zone()、note() 和 arrow(a, b, label, {waypoints, dashed, color});arrow() 会绑定两端,并通过绝对路径点进行路由(保持路径点轴对齐,以获得整洁的正交线条)。fromMermaid() 可将 Mermaid 转换为标准类型的图表(可选依赖项)。
下文的元素格式参考仍然很重要 — 它说明了构建器会生成什么、小型场景如何手动编辑,以及如何理解现有文件。
文件封装
每个 .excalidraw 文件都是以下顶层对象:
{
"type": "excalidraw",
"version": 2,
"source": "your-tool-name",
"elements": [ /* the scene, in draw order */ ],
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
"files": {}
}
type/version是固定值(当前架构为"excalidraw"/2)。source是由写入方标记的自由字符串(例如"my-tool")。elements是整个场景——数组顺序就是绘制顺序(越靠后的元素越在上层)。矩形应放在其上的文本/箭头之前。appState——保持精简;大多数文件只需viewBackgroundColor字段。files——嵌入图像的二进制数据,以 file-id 为键。对于示意图,使用空对象{}。
元素模型
元素是根据 type 区分的联合类型。每个元素都包含以下公共块(下面的值是安全默认值):
| 字段 | 类型 | 默认值/说明 |
|---|---|---|
id | string | 在场景中唯一。任何字符串均可("el-1"、nanoid 等)。 |
type | string | "rectangle" | "text" | "arrow"(还有其他类型;这三种适用于大多数示意图)。 |
x,y | number | 画布上的左上角。Y 向下递增,X 向右递增,单位为 px。 |
width,height | number | 边界尺寸。对于箭头,这是 points 的范围。 |
angle | number | 旋转角度,单位为弧度。0 = 正向。 |
strokeColor | string | 轮廓/文本颜色。默认值为 #1e1e1e。 |
backgroundColor | string | 填充色。未填充的形状使用 "transparent"。 |
fillStyle | string | "solid" | "hachure" | "cross-hatch"。使用 "solid"。 |
strokeWidth | number | 1 细,2 粗(默认值),4 特粗。 |
strokeStyle | string | "solid" | "dashed" | "dotted"。 |
roughness | number | 草图效果:0 建筑师(整洁),1 艺术家(默认值),2 漫画家。 |
opacity | number | 0–100。 |
groupIds | string[] | 分组成员身份——参见 分组。[] = 未分组。 |
frameId | string | null | 所属框架的 ID,或 null。 |
roundness | object | null | 圆角/曲线样式——参见下方各类型的说明。 |
seed | number | 任意整数(Excalidraw 的手绘随机数生成器种子)。任意整数均可。 |
versionNonce | number | 任意整数;每次编辑时都会变化。 |
version | number | 编辑计数器;从 1 开始,每次更改时递增。 |
isDeleted | boolean | false。软删除的元素会以 true 状态保留在文件中。 |
boundElements | array | 对绑定箭头/标签的反向引用——参见 绑定。 |
updated | number | Epoch 毫秒时间戳(或用于确定性输出的任意单调整数)。 |
link | string | null | 可选超链接。 |
locked | boolean | false。 |
index(z-order): Excalidraw 编辑器会为每个元素添加一个分数索引字符串("a0"、"a1"……),以追踪堆叠顺序。手动编写时请省略此项——编辑器会在加载时根据数组顺序分配索引。不要只为部分元素设置此项;不完整的索引会破坏 z-order。
rectangle
{ "type": "rectangle", "roundness": { "type": 3 }, /* + common fields */ }
roundness: { "type": 3 }→ 圆角。roundness: null→ 直角。- 带标签的方框由一个矩形加上一个独立的文本元素组成(参见 标签)。
text
{
"type": "text",
"text": "Login service",
"originalText": "Login service",
"fontSize": 16,
"fontFamily": 2,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"lineHeight": 1.25,
"baseline": 13,
"autoResize": true,
"roundness": null
/* + common fields */
}
text是实际渲染的内容;originalText与其内容相同(Excalidraw 会同时保留这两个字段——除非你清楚原因,否则请保持二者一致)。fontFamily:1= 手绘风格(Virgil),2= 常规(Helvetica,默认值),3= 代码(Cascadia/等宽字体)。width/height必须与渲染后的文本相符,否则编辑器在重新计算前会显示异常。没有字体度量数据时,可使用以下近似值:width ≈ 最长行字符数 × fontSize × 0.58,height ≈ 行数 × fontSize × 1.25。baseline ≈ round(fontSize × 0.8)。设置autoResize: true可让编辑器在加载时修正尺寸。textAlign:"left" | "center" | "right"。verticalAlign:"top" | "middle" | "bottom"。
箭头
{
"type": "arrow",
"points": [[0, 0], [0, 80]],
"lastCommittedPoint": null,
"startBinding": { "elementId": "el-1", "focus": 0, "gap": 4 },
"endBinding": { "elementId": "el-2", "focus": 0, "gap": 4 },
"startArrowhead": null,
"endArrowhead": "arrow",
"roundness": { "type": 2 }
/* + common fields */
}
points相对于箭头自身的x/y。 第一个点是[0, 0];箭头的x/y就是该点在画布绝对坐标中的位置。width/height是这些点的边界范围。常见错误是存储绝对坐标的点——不要这样做。- 多段(正交/肘形)箭头只需添加更多点:
[[0,0],[0,40],[120,40]]会绘制一条先向下再向右的线。为确保路径整洁,请让线段沿轴对齐(纯水平或垂直)。 roundness: { "type": 2 }会将转角变圆;null会保留尖锐的肘形转角。endArrowhead/startArrowhead:"arrow"(末端默认值)、null(无箭头),或"triangle"、"dot"、"bar"。- 使用
strokeColor为箭头设置颜色(柔和的#495057作为默认颜色很清晰)。
绑定 — 让箭头跟随形状
绑定是双向且相互对应的。要将箭头绑定在两个形状之间,必须修改三个位置,否则形状移动时箭头不会跟随:
arrow.startBinding = { elementId: from.id, focus: 0, gap: 4 }arrow.endBinding = { elementId: to.id, focus: 0, gap: 4 }- 在每个形状的
boundElements中添加反向引用:from.boundElements.push({ id: arrow.id, type: "arrow" }),并对to执行相同操作。
focus(-1..1)表示箭头在绑定边上的连接位置;0= 居中。gap是箭头尖端与形状之间保留的间距(2–6 px 效果较好)。- 如果只设置箭头的绑定,而不设置形状的
boundElements,箭头虽然会渲染,但形状一被拖动就会脱离。务必同时设置两个方向。 - 优先使用构建器。
diagram.arrow(a, b)会通过官方编译器为你完成全部三项设置——只有在编辑原始 JSON 时才手动编写绑定。无论使用哪种方式,lint-scene.mjs现在都会对任何未绑定、单向绑定或悬空的箭头报错(退出码为 1),因此在确认场景完成前请先运行它:移动时不会跟随的箭头属于错误,而不是样式小问题。
标签 — 框内文本(两种方式)
A. 容器绑定标签(Excalidraw 原生方式)。 文本会成为子元素,由编辑器自动在框内居中并换行:
- 文本:设置
containerId = rect.id,并将其放在矩形内。 - 矩形:
boundElements包含{ "type": "text", "id": text.id }。 编辑器会在框调整大小时重新排布标签。适合由人工继续编辑的场景。
B. 自由浮动标签(确定性方式)。 文本是独立元素,通过绝对 x/y 放置在框内,containerId: null,并且不在框的 boundElements 中引用。生成起来更简单,且完全可预测,但不会自动重新排布。适用于生成的静态图表。常见做法是制作一张“卡片”:一个圆角矩形、左上方附近的标题文本,以及下方可选的较小字号正文,三者使用相同的 groupIds。
分组
组只是每个成员的 groupIds 中共享的 id——不存在单独的组对象。要对元素分组,将相同的组 id 字符串赋给它们各自的 groupIds。嵌套组会叠加 id(最外层在最后)。若要在视觉上绘制带标签的“容器”,可在成员后方添加虚线矩形(strokeStyle: "dashed"、柔和的描边、backgroundColor: "transparent")——它就是一个普通矩形,也可以选择将其放在同一组中。
程序化路径: diagram.group(ids, { title }) 会将已放置的卡片组成一个真实的组——它会把共享的 groupId 写入每张卡片的所有部分(矩形 + 标题 +
正文),并在后方绘制可选的虚线框和标签。需要让多个区块作为一个整体移动时,请使用它;传入与 card() 相同的 ids。(groupIds 会在 convertToExcalidrawElements 后保留,因此构建器只需将其设置到骨架上。)
坐标与布局约定
- 无限画布;可任意选择原点(例如从
0,0附近开始)。Y 轴向下。 - 按
x值递增从左向右排列列,按y值递增从上向下堆叠行。 - 在框之间留出间隔(例如列间留 80–90px,堆叠框之间留约 28px),为箭头留出空间,避免箭头穿过无关形状。
- 想让箭头清晰易读,不要让框重叠。框重叠以及箭头穿过无关框,通常会导致场景杂乱。
- 适合 Excalidraw 的易读调色板:默认描边
#1e1e1e;柔和灰色#495057/#868e96/#adb5bd;强调色蓝色#1971c2、橙色#e8590c、紫罗兰色#7048e8、 绿色#2f9e44。用颜色编码含义(例如每种边类型使用一种色相),而不是单纯装饰。
阅读现有场景
解析 JSON 并遍历 elements。要生成精简的大纲(比原始 JSON 更便于模型理解):
- 矩形 → 标签是
containerId等于矩形 id 的文本,或位于矩形边界内的独立文本。 - 箭头 → 将
startBinding.elementId/endBinding.elementId解析为对应形状的标签,并渲染为Arrow: "A" → "B"。 - 跳过
isDeleted: true的元素。对超大型场景进行截断(例如只取前约 200 个元素)。
大纲行示例:Rectangle "Login"、Arrow: "Login" → "Session" (label: "auth")。
更新场景中的部分内容
对现有文件实际执行的三种操作:
1. 在现有内容下方追加新区块
不要猜测坐标——先测量并移动:
- 计算现有元素的联合边界框(
minX/minY和maxX = max(x+width)、maxY = max(y+height))。 - 计算新增元素的联合边界框。
dy = existing.maxY + gap − incoming.minY(约 60px 的gap效果较好)。dx = 0。- 将每个新增元素平移
(dx, dy)——只需将其加到各自的x/y上。由于箭头的points是相对于箭头自身的x/y定义的,移动x/y就会移动整个箭头;无需重写点坐标。绑定引用 id,因此移动后仍然有效。 - 拼接:
existing.concat(shiftedIncoming)。
2. 替换某个区域
- 确定要移除的元素(按 id,或按共同的
groupIds条目)。 - 从
elements中移除这些元素(硬删除),或设置isDeleted: true以保留历史记录。 - **重新连接悬空箭头:**对于
startBinding/endBinding指向已移除形状的箭头,要么也删除该箭头,要么将绑定的elementId改为指向替换形状,并更新该形状的boundElements。同时从保留形状的boundElements中移除引用已删除箭头的过期条目。 - 插入替换元素(并按照绑定为新箭头建立绑定)。
3. 就地编辑元素
直接修改字段,然后标记变更,以便编辑器正确完成同步:
- 增加
version(version + 1),将versionNonce设置为新的整数,并将updated设置为当前时间。 - 对于文本变更,同时更新
text和originalText,并重新计算width/height(或使用autoResize: true)。 - 对于移动/调整大小,更新
x/y/width/height;绑定的箭头会通过绑定自动跟随(无需编辑箭头)。 - 要更改颜色,请修改
strokeColor/backgroundColor。
注意事项(真正会破坏场景的问题)
程序化路径(convertToExcalidrawElements)会自动处理前四项——为你设置双向绑定、相对点、文本尺寸和 z 轴顺序。这些问题主要会在手动编写时出现。
- 单向绑定 → 拖动时箭头会脱落。务必同时设置箭头的
*Binding和形状的boundElements。 - 箭头点使用绝对坐标 → 箭头会渲染在错误位置。点是相对坐标;第一个点为
[0,0]。 - 缺少文本
width/height→ 标签尺寸不正确,直到重新加载才会恢复。请估算尺寸,或设置autoResize: true。 index字段不完整 → z 轴顺序损坏。手动编写时请完全省略index。- 方框重叠/箭头穿过无关形状 → 图表难以阅读。请保留间距,并使用正交线路。
- 软删除残留项 → 即使形状的
isDeleted: true,其他位置的boundElements仍可能包含指向它的条目;如果想让绑定的箭头消失,请清理这些条目。 text/originalText不一致 → 除非有意设置为不同值,否则请保持二者相同。
参考资料与示例
references/element-reference.md— 完整的逐字段表格、值枚举(fontFamily、arrowhead、roundness、appState 键)以及绑定/分组语义。references/minimal-scene.excalidraw— 完整且可加载的双框箭头场景,可复制并调整。scripts/— 程序化工具链:excalidraw-lib.mjs(Diagram构建器 +writeScene/writeSvg/fromMermaid)、render.mjs(忠实的 SVG 导出)、lint-scene.mjs(几何检查器)、setup.sh(一次性安装 + 打包)。请参阅scripts/README.md。examples/architecture.example.mjs— 使用构建器构建的可运行端到端图表。
真相来源
上述元素格式是 Excalidraw 实际可加载的最小子集,已通过 Excalidraw 0.18.1 的场景验证。编辑器在往返处理时可能会添加或保留额外字段(例如 index、自定义的逐元素数据);此处记录的字段足以编写编辑器可正常打开的文件。如果不确定此技能未涵盖的某个字段,请在 Excalidraw 编辑器中绘制一次该形状,保存并检查生成的 JSON——经过往返处理的文件才是最终依据。