这篇指南演示如何用 Fabric.js 保存 JSON,再把它加载回来。画布、工具栏和界面都还是你自己的,引擎只负责它们周围的文档工作。
安装
这个包支持 Fabric.js 6 和 7。用 npm 把它和 fabric 一起安装:
npm install fabricjs-document-engine fabric保存和加载
把引擎连接到你已有的画布。toDocument() 会把整个画布返回为普通 JSON,loadDocument() 再把它放回去:
import { Canvas, Rect } from "fabric";
import { createDocumentEngine } from "fabricjs-document-engine";
const canvas = new Canvas("editor", { width: 800, height: 600 });
const engine = createDocumentEngine({ canvas });
canvas.add(new Rect({ width: 100, height: 80, fill: "tomato" }));
const document = engine.toDocument();
localStorage.setItem(document.id, JSON.stringify(document));
await engine.loadDocument(JSON.parse(localStorage.getItem(document.id)!));在下面试一试。先加几个图形,点按钮用 Fabric.js 保存 JSON,再随便移动一下,最后点「Load」从 JSON 回显保存时的样子。这就是加载 JSON 的完整过程。
正在加载在线示例,代码在下方。
"use client";
import type { FabricDocument } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { CircleIcon, FolderOpenIcon, SaveIcon, SquareIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import {
DemoCanvas,
DemoFrame,
newShape,
seedShapes,
Status,
Toolbar,
ToolButton,
} from "./demo-ui";
import { useFabricCanvas } from "./use-fabric-canvas";
export default function SaveLoadDemo() {
const t = useTranslations("demos");
const { elementRef, canvas } = useFabricCanvas();
const engine = useDocumentEngine(canvas);
const [saved, setSaved] = useState<FabricDocument>();
useEffect(() => {
if (!engine) return;
engine.canvas.add(...seedShapes());
engine.clearHistory();
}, [engine]);
function save() {
// The whole canvas as plain JSON. Store it anywhere.
setSaved(engine?.toDocument());
}
async function load() {
if (saved) await engine?.loadDocument(saved);
}
return (
<DemoFrame>
<DemoCanvas elementRef={elementRef} label={t("canvas")} />
<Toolbar>
<ToolButton
icon={SquareIcon}
label={t("square")}
onClick={() => canvas?.add(newShape("square"))}
/>
<ToolButton
icon={CircleIcon}
label={t("circle")}
onClick={() => canvas?.add(newShape("circle"))}
/>
<span className="ml-auto flex gap-1.5">
<ToolButton
icon={SaveIcon}
label={t("saveLoad.save")}
primary
disabled={!engine}
onClick={save}
/>
<ToolButton
icon={FolderOpenIcon}
label={t("saveLoad.load")}
disabled={!saved}
onClick={() => void load()}
/>
</span>
</Toolbar>
<Status tone={saved ? "ok" : "idle"}>
{saved
? t("saveLoad.saved", { count: saved.objects.length })
: t("saveLoad.hint")}
</Status>
<pre className="h-32 overflow-auto rounded-lg border bg-muted p-3 font-mono text-xs">
{saved ? JSON.stringify(saved, null, 2) : "{}"}
</pre>
</DemoFrame>
);
}保存的 JSON 里每个对象都有稳定的 id。移动、编组、保存、重新打开之后它都不会变,所以你可以把对象和自己的数据关联起来。
顺带得到的功能
创建引擎的同时也打开了撤销重做。每个用户操作,比如添加、移动或缩放图形,都是一步撤销:
await engine.undo();
await engine.redo();保存到存储
在真实项目里,给引擎一个存储适配器。这样 save() 和 load(id) 就能用了,自动保存也会在用户编辑时帮你保存文档:
import { createDocumentEngine } from "fabricjs-document-engine";
import { createLocalStorage } from "fabricjs-document-engine/storage";
const engine = createDocumentEngine({
canvas,
storage: createLocalStorage({ prefix: "my-app:" }),
autosave: true,
});
await engine.load("project-42");
await engine.save();想把 Fabric.js 保存 JSON 的结果存进你自己的数据库,也是同样的代码。详见存储适配器。