首页
/ 如何用 onChange 回调把 Excalidraw 场景数据持久化到后端或本地存储?

如何用 onChange 回调把 Excalidraw 场景数据持久化到后端或本地存储?

2026-09-08 16:43:46作者:冯爽妲Honey

@excalidraw/excalidraw 嵌入自己的应用后,画布上的元素只存在于组件内部状态中,刷新页面就会丢失。Props 文档给出的官方路径是:给 Excalidraw 组件传一个 onChange 回调,在组件因任何变化而更新时拿到场景的 elementsappStatefiles,由你在回调内把它们保存到自己的后端或本地存储,加载时再用 initialData 把场景还原回去。

准备条件

安装文档 安装包:

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

两个会影响渲染的前提:

  • Excalidraw 会占满父容器的 100% 宽度和高度,渲染它的容器必须是非零尺寸,文档中的示例容器统一给了固定高度(如 style={{ height: "500px" }})。
  • 集成文档说明 Excalidraw 不支持服务端渲染。如果你用 Next.js,需要用动态导入并关闭 SSR:
import dynamic from "next/dynamic";
import "@excalidraw/excalidraw/index.css";

const Excalidraw = dynamic(
  async () => (await import("@excalidraw/excalidraw")).Excalidraw,
  {
    ssr: false,
  },
);

onChange 回调的三个参数

Props 文档定义了 onChange 的签名和触发时机:

(excalidrawElements, appState, files) => void;
  • excalidrawElements:场景中所有 Excalidraw 元素数组;
  • appState:场景当前的 AppState
  • files:添加到场景中的 BinaryFiles

文档明确说明该回调在组件因任何变化而更新时触发,并原话建议:“Here you can try saving the data to your backend or local storage for example.”(你可以在这里尝试把数据保存到后端或本地存储。)也就是说,持久化逻辑完全由宿主应用接管,包本身不提供存储实现。

接入 onChange 并验证回调触发

仓库内的示例应用 ExampleApp.tsx 展示了官方示例的接法,它把回调里收到的数据打印到控制台:

onChange: (
  elements: NonDeletedExcalidrawElement[],
  state: AppState,
) => {
  console.info("Elements :", elements, "State : ", state);
},

把这段模式放进你的组件,然后在画布上添加、移动、删除元素,观察浏览器控制台是否随每次变化打印出最新的 elementsstate——这是示例仓库用来确认回调正常触发的方式。确认回调按预期触发后,再替换为真正的持久化逻辑。

把场景数据写入存储目标

文档只约定了回调会拿到哪三份数据,具体写入方式由你的存储目标决定。以下是在回调内做本地持久化的最小示意(localStorage 的 key 可自行替换):

<Excalidraw
  onChange={(elements, appState, files) => {
    const scene = { elements, appState, files };
    localStorage.setItem("excalidraw-scene", JSON.stringify(scene));
  }}
/>

如果是保存到后端,同样是把这三个参数序列化为请求体发出,例如按你的接口约定发送 { elements, appState, files }。注意 files 不要省略:场景里的图片等二进制文件同样需要持久化,否则下次加载时元素会引用不到文件数据。

替代路径:通过 excalidrawAPI 订阅变更

excalidrawAPI 文档提供了等价于 props.onChange 的订阅方式,适合回调需要在组件挂载之后动态注册/注销的场景:

(
  callback: (
    elements: readonly ExcalidrawElement[],
    appState: AppState,
    files: BinaryFiles,
  ) => void
) => () => void

它返回一个 unsubscribe 函数,用于取消订阅。使用时先通过 excalidrawAPI prop 把 API 存到 state,再调用 api.onChange(callback)

export default function App() {
  const [excalidrawAPI, setExcalidrawAPI] = useState(null);

  useEffect(() => {
    if (!excalidrawAPI) {
      return;
    }
    const unsubscribe = excalidrawAPI.onChange(
      (elements, appState, files) => {
        console.info("Elements :", elements, "State : ", appState);
      }
    );
    return unsubscribe;
  }, [excalidrawAPI]);

  return (
    <div style={{ height: "500px" }}>
      <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
    </div>
  );
}

下次加载时还原场景

持久化只是半边工作,initialData prop 负责把存下的数据加载回画布。initialData 文档支持 elementsappStatefiles 三个字段,可选对象或一个解析为该对象的 Promise。示意(savedScene 是从存储读出的 { elements, appState, files } 对象):

<Excalidraw
  initialData={{
    elements: savedScene.elements,
    appState: savedScene.appState,
    files: savedScene.files,
  }}
/>

如果存下的数据来自外部导入(缺字段),restore 工具文档提供了 restoreElements / restoreAppState / restore,它们会为缺失的属性补上默认值,保证元素和状态完整:

import { restoreElements } from "@excalidraw/excalidraw";

仓库示例 ExampleApp.tsxupdateScene 的用法演示了这条链路:先 restoreElements(convertToExcalidrawElements([...]), null) 规整元素,再连同 appState 一起传给 excalidrawAPI.updateScene(sceneData) 更新场景。

加载后,在画布上确认之前保存的元素、背景和文件都已出现,说明还原成功;再改动一次画布,确认 onChange 携带的新数据已写入存储目标,整条保存—加载闭环就跑通了。

边界与注意事项

  • getFiles / 回调中的 files 可能包含没有被任何元素引用的文件。API 文档建议:如果你要把文件持久化到存储,应当与已存储的元素做比对后再决定保存哪些文件。
  • initialData 文档提示:如果不使用 scrollToContent(默认不滚动),需要同时传入 initialData.appState.scrollXinitialData.appState.scrollY 才能保留滚动位置。
  • 若需要把持久化数据同时用于协作、在紧循环里频繁恢复元素,restoreElementsrefreshDimensions 选项(默认 false)可用于关闭文本尺寸重算这类较昂贵的操作,具体见 restore.mdx
  • v0.17.0 起 Ref 支持已被移除,接入统一使用 excalidrawAPIready / readyPromise 也已停用(见 excalidrawAPI 文档)。

文档未给出存储节流、增量保存等策略;onChange 在组件每次因变化更新时都会触发,保存频率和后端接口设计需要按你的应用自行处理,本文不预设这些方案。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
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