首页
/ 如何读写 Excalidraw 的 .excalidraw JSON 文件格式(elements / appState / files)?

如何读写 Excalidraw 的 .excalidraw JSON 文件格式(elements / appState / files)?

2026-09-08 17:19:38作者:蔡怀权

如果你的应用需要处理 Excalidraw 的场景数据——把用户保存的 .excalidraw 文件(明文 JSON)加载进画布,或者把画布当前内容持久化回 .excalidraw 文件——你需要理解这个文件的三个核心字段:elementsappStatefiles,并知道读写两侧分别调用哪些 API。本文基于 Excalidraw 仓库内的开发者文档,给出一条可执行的路径:安装依赖 → 解析文件 → 用 initialData/restore 写入画布 → 用 excalidrawAPI 读回场景并组装成合规 JSON。

准备条件

安装文档,Excalidraw 是作为组件嵌入你的项目,使用 npmyarn 安装:

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

另外注意:Excalidraw 会占满容器 width/height 的 100%,所以渲染它的容器必须具有非零尺寸,否则画布不可见,你也会无法目视验证加载结果。

.excalidraw 文件由哪些字段组成

JSON Schema 文档,当场景保存到本地文件时,.excalidraw 文件使用如下格式:

属性 说明
type Excalidraw schema 的类型 "excalidraw"
version Excalidraw schema 的版本 number
source Excalidraw 应用的来源 URL "https://excalidraw.com"
elements 画布上元素的对象数组 包含 excalidraw element 对象的数组
appState 附加的应用状态/配置 包含应用状态属性的对象
files image 元素的数据 图片数据对象,格式为 { [fileId]: fileData }

文档给出的 JSON 示例(节选,version: 2 为文档示例值):

{
  "type": "excalidraw",
  "version": 2,
  "source": "https://excalidraw.com",

  "elements": [
    {
      "id": "pologsyG-tAraPgiN9xP9b",
      "type": "rectangle",
      "x": 928,
      "y": 319,
      "width": 134,
      "height": 90
      /* ...other element properties */
    }
  ],

  "appState": {
    "gridSize": 20,
    "viewBackgroundColor": "#ffffff"
  },

  "files": {
    "3cebd7720911620a3938ce77243696149da03861": {
      "mimeType": "image/png",
      "id": "3cebd7720911620a3938c.77243626149da03861",
      "dataURL": "data:image/png;base64,iVBORWOKGgoAAAANSUhEUgA=",
      "created": 1690295874454,
      "lastRetrieved": 1690295874454
    }
  }
}

在源码层面,这个结构与 ExportedDataState 类型对应(type / version / source / elements / appState / files),而读入一侧对应 ImportedDataState——后者所有字段可选,因此你解析得到的 JSON 中缺失字段时,需要交给下一节的 restore 补全,而不是自己猜默认值。

读取:把 JSON 写入画布

第一步:用 initialData 挂载场景

<Excalidraw /> 组件提供 initialData prop,用于加载初始场景。它必须是一个对象,或一个解析为对象的 Promise,包含以下可选字段:

字段 类型 说明
elements ExcalidrawElement[] Excalidraw 挂载时使用的元素
appState AppState Excalidraw 挂载时使用的 AppState
scrollToContent boolean 挂载后是否把最近元素滚动到居中;默认不滚动。scrollToContentfalse 时,请同时传入 initialData.appState.scrollXinitialData.appState.scrollY,滚动位置才能保留
files BinaryFiles 添加到场景的 files

最小可用的读取示例(来自文档的 live 示例,矩形坐标、颜色等值照抄文档):

function App() {
  return (
    <div style={{ height: "500px" }}>
      <Excalidraw
        initialData={{
          elements: [
            {
              type: "rectangle",
              version: 141,
              versionNonce: 361174001,
              isDeleted: false,
              id: "oDVXy8D6rom3H1-LLH2-f",
              fillStyle: "hachure",
              strokeWidth: 1,
              strokeStyle: "solid",
              roughness: 1,
              opacity: 100,
              angle: 0,
              x: 100.50390625,
              y: 93.67578125,
              strokeColor: "#000000",
              backgroundColor: "transparent",
              width: 186.47265625,
              height: 141.9765625,
              seed: 1968410350,
              groupIds: [],
            },
          ],
          appState: { zenModeEnabled: true, viewBackgroundColor: "#a5d8ff" },
          scrollToContent: true,
        }}
      />
    </div>
  );
}

由于 initialData 支持 Promise 形式,从后端或本地读取 .excalidraw 文件后再解析 JSON、以 Promise 传给 initialData 也是文档允许的形态(文件加载部分由你自己的实现提供,文档未规定具体取数方式)。

第二步:用 restore 补全缺失字段

外部 .excalidraw 文件里的元素可能缺少新增版本引入的属性。Restore 工具文档 提供三个互补的函数,均从 @excalidraw/excalidraw 导入:

import { restore, restoreAppState, restoreElements } from "@excalidraw/excalidraw";
  • restore(data, localAppState, localElements, opts):确保 elements 和 appState 都有合适的值,缺失时设为默认值,是 restoreElements + restoreAppState 的组合。localAppStatePartial<AppState>localElementsExcalidrawElement[],两者都可传 null/undefined
  • restoreAppState(appState, localAppState):确保 appState 的所有 key 都有合适 value,缺失的 key 设为默认值。若传入了 localAppState,则用它的值替代 appState 中缺失(undefined)的项,而不是用默认值——文档建议用它来避免覆盖你已持久化的用户默认设置。
  • restoreElements(elements, localElements, opts):确保元素属性完整,缺失属性设为默认值。传入 localElements 时,已有元素会复用 version(并自增)并重新生成 versionNonce;文档说明当导入的元素可能已存在于场景中、且你依赖 element version 检测更新时应使用它。

opts 有三个开关,含义都直接来自文档:

参数 类型 文档说明
refreshDimensions boolean 是否重新计算文本元素尺寸。这是潜在的高开销操作,如果你在协作等紧密循环中恢复元素,可以考虑关闭
repairBindings boolean 是否修复元素绑定,确保不存在指向缺失 bound text 的容器、或指向缺失容器的 bound text
normalizeIndices boolean 是否归一化元素的分数字母索引(fractional indices),防止使用过旧/过长的索引引发问题

写回:从画布读回 elements / appState / files

场景的读取通过 excalidrawAPI 完成。按 excalidrawAPI 文档,先通过回调拿到 API 并保存到 state:

export default function App() {
  const [excalidrawAPI, setExcalidrawAPI] = useState(null);
  return <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />;
}

与“写出 .excalidraw JSON”直接相关的三个 API:

  • getSceneElements():返回场景中所有未删除的元素(NonDeleted<ExcalidrawElement>[]);如需包含已删除元素用 getSceneElementsIncludingDeleted()
  • getAppState():返回当前 AppState
  • getFiles():返回场景中的 files。文档特别提醒:它可能包含没有任何元素引用的文件,所以持久化前应对比已存储的元素——即只保留仍被元素引用的 files 条目。

三者的结果加上 type / version / source 头部字段,就是上一节定义的 .excalidraw 文件结构,可整体写入文件存储。若需要随场景变化持续同步,可用 excalidrawAPI.onChange(callback) 订阅变更事件,回调签名为 (elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles) => void,返回一个 unsubscribe 函数。

程序化写入场景时的撤销行为

如果你不是通过 initialData 挂载,而是在运行时用 excalidrawAPI.updateScene(sceneData) 更新场景(sceneDataelementsappStatecaptureUpdate 等字段),用 captureUpdate 控制撤销/重入栈行为:

文档说明
CaptureUpdateAction.IMMEDIATELY 应被捕获的更新,大多数本地更新使用该值,会立即进入本地 undo/redo 栈
CaptureUpdateAction.EVENTUALLY 不应立即捕获的更新,适用于异步多步过程中的中间状态
CaptureUpdateAction.NEVER 永不应记录的更新,如远程更新或场景初始化

文档同时注明:对 collaborators 或未观察(非 ObservedAppState)的 AppState 部分的更新永远不会进入 undo/redo 栈,与传入的 captureUpdate 无关。

验证与边界

写回后的验证标准来自格式定义本身:解析你持久化的 JSON,确认顶层含 type: "excalidraw"、数字 versionsource,且 elements 为数组、appState 为对象、files{ [fileId]: fileData } 结构(见 JSON Schema 文档)。加载侧的验证则是挂载后画布上出现 initialData.elements 中的元素(上面 live 示例即演示了这一效果)。

两个容易踩的边界:

  1. 剪贴板格式 ≠ 文件格式。复制选区到剪贴板时 JSON 结构类似,但 type"excalidraw/clipboard",且属性不同(无 source,元素数组与 files 语义类似)。如果你的应用要接受剪贴板粘贴的数据,不能直接按 .excalidraw 文件的字段处理。
  2. 滚动位置保留:如前述,scrollToContent: false 时必须同时提供 appState.scrollX / appState.scrollY,否则滚动位置不会被保留。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391