Fabric.js Document Engine

搜索文档

查找页面或章节

EN

快速开始

查看 Markdown

安装这个包,连接你现有的画布,然后保存它。

这篇指南演示如何用 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 的完整过程。

正在加载在线示例,代码在下方。

save-load-demo.tsx
"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 的结果存进你自己的数据库,也是同样的代码。详见存储适配器。

下一步