如何用 exportToBlob 把 Excalidraw 场景以代码方式导出为 PNG 图片?
在把 @excalidraw/excalidraw 集成进自己的 React 应用后,如果想在用户点击按钮(或任意程序逻辑)时把当前画布上的内容导出成 PNG 文件,而不是依赖 Excalidraw 自带的界面菜单,就需要直接调用库导出的 exportToBlob 工具函数。它接收场景元素和 appState,返回一个以 Blob 类型解析的 Promise,内部通过 canvas.toBlob 完成生成。本文给出从安装依赖、获取 API 实例到导出并验证 PNG 的完整代码路径。
准备条件:安装包并确保容器有尺寸
用 npm 或 yarn 安装 React 与 Excalidraw 包:
npm install react react-dom @excalidraw/excalidraw
# 或
yarn add react react-dom @excalidraw/excalidraw
两个容易忽略的前置条件,来自安装文档:
- Excalidraw 组件会占满容器 100% 的
width和height,渲染它的容器必须有非零尺寸,否则画布不可用。 - 默认情况下 Excalidraw 会从 CDN 下载字体;如果应用要离线或自托管运行,需要把
node_modules/@excalidraw/excalidraw/dist/prod/fonts的内容复制到静态资源目录,并把window.EXCALIDRAW_ASSET_PATH指向该路径(资源在站点根目录时为"/")。字体加载影响导出结果中的文字渲染,离线环境建议提前配置。
第一步:通过 excalidrawAPI 拿到场景数据
exportToBlob 需要三个输入:场景元素、appState 和文件数据。这三者都通过 excalidrawAPI prop 暴露的实例方法获取(参见 excalidrawAPI 文档):
| 方法 | 用途 |
|---|---|
getSceneElements() |
返回场景中未删除的全部元素 |
getAppState() |
返回当前 appState |
getFiles() |
返回场景中的文件数据(可能包含没有被任何元素引用的文件,若要把文件持久化存储,需要先与已存元素比对) |
回调触发后要把 api 存进 state 以便后续使用:
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
return (
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
);
}
注意:自 v0.17.0 起 Ref 支持已被移除,旧代码里用 ref 拿实例的写法必须改为 excalidrawAPI 回调。
第二步:调用 exportToBlob 导出 PNG
exportToBlob 从 @excalidraw/excalidraw 导入:
import { exportToBlob } from "@excalidraw/excalidraw";
其签名为 exportToBlob(opts: ExportOpts & { mimeType?: string, quality?: number, exportPadding?: number }),其中 opts 会整体传给 exportToCanvas。完整示例(元素列表参考 仓库示例应用 中 "Export to Blob" 按钮的写法):
import { useState } from "react";
import { Excalidraw, exportToBlob } from "@excalidraw/excalidraw";
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
const [blobUrl, setBlobUrl] = useState("");
const onExportPng = async () => {
if (!excalidrawAPI) {
return;
}
const elements = excalidrawAPI.getSceneElements();
if (!elements || !elements.length) {
return;
}
const blob = await exportToBlob({
elements,
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
mimeType: "image/png",
});
setBlobUrl(window.URL.createObjectURL(blob));
};
return (
<div>
<button onClick={onExportPng}>Export to Blob (PNG)</button>
<img src={blobUrl} alt="exported scene" />
<div style={{ width: 400, height: 400 }}>
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
</div>
);
}
各参数及默认值(引自 Export Utilities 文档):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
elements |
ExcalidrawElement[] |
_ | 要导出的元素,本例用 getSceneElements() 取未删除元素 |
appState |
AppState |
默认 App State | 场景的 app state,本例用 getAppState() 取当前值 |
files |
BinaryFiles |
_ | 场景中添加的文件(图片等),用 getFiles() 获取 |
mimeType |
string |
image/png |
图片格式;导出 PNG 时可显式传 "image/png" 或省略 |
quality |
number |
0.92 |
0~1 之间的画质值,仅对 image/jpeg/image/webp 生效,导出 PNG 时会被忽略 |
exportPadding |
number |
10 |
画布四周添加的 padding |
appState 中还有几个只影响导出结果的属性("Additional attributes of appState for export* APIs"):
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
exportBackground |
boolean |
true |
是否导出背景色 |
viewBackgroundColor |
string |
#fff |
背景色 |
exportWithDarkMode |
boolean |
false |
是否以暗色模式导出 |
exportEmbedScene |
boolean |
false |
是否把场景数据嵌入 png/svg 中,会使图片体积增大 |
如果需要暗色导出,把 appState 改成 { ...excalidrawAPI.getAppState(), exportWithDarkMode: true } 即可。
可选:控制导出画布的尺寸
opts 继承自 exportToCanvas 的参数,因此还可以传入画布尺寸控制项(见同一文档的 exportToCanvas 与 getDimensions 小节):
getDimensions:一个函数,接收场景内容的width/height,返回实际导出用的{ width, height, scale? }(scale默认为1)。文档示例中固定导出 350×350 的写法是getDimensions: () => { return { width: 350, height: 350 }; }。maxWidthOrHeight:限定导出图片的最大宽或高;提供后getDimensions会被忽略(源码在两者同时传入时会在控制台警告 "getDimensions()is ignored whenmaxWidthOrHeightis supplied.")。
验证导出结果
文档给出的操作路径是:导出完成后用 window.URL.createObjectURL(blob) 生成 URL,赋给 <img> 的 src,页面上出现与画布一致的图片即表示成功(上例代码即如此,示例应用 的 export-blob 区域也是同样的显示方式)。
失败时 exportToBlob 的 Promise 会 reject:查看 实现源码,当底层 canvas.toBlob 没有产生结果时会 reject(new Error("couldn't export to blob")),可以用 try/catch 捕获该错误提示用户重试。
限制与常见坑
quality对 PNG 无效:源码里对mimeType === "image/png"且传了quality的情况会打印警告"quality" will be ignored for "image/png" mimeType,调 PNG 画质没有意义。- 导出 JPEG 时若
exportBackground为false,实现会自动把exportBackground置为true并打警告,因为 JPEG 不支持透明背景。 mimeType写成"image/jpg"会被内部自动纠正为image/jpeg,可以放心使用。getFiles()返回的文件可能包含未被元素引用的文件,只用于本次导出的files参数没有问题;若还要把它们持久化存储,需自行与已存元素比对。- 旧版 ref 写法在 v0.17.0 起不可用,
ready/readyPromise也已移除,统一改用excalidrawAPI回调。
更多导出工具(exportToCanvas、exportToSvg、exportToClipboard)的签名与参数见 Export Utilities 文档;exportToBlob 本身就是先走 exportToCanvas 拿到 canvas 再调 canvas.toBlob,因此上面 exportToCanvas 小节的参数说明同样适用于本场景。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00