# 编辑器教程

> Fabric.js 使用教程：用 TypeScript 做一个画布编辑器，支持添加图形、撤销重做、自动保存和导出 PNG。

Source: https://fabricjs-document-engine.jscrate.dev/zh/docs/overview/tutorial
Last updated: 2026-09-28

这篇 Fabric.js 使用教程会带你用 TypeScript 做一个小型画布编辑器。它可以添加图形、撤销重做、自动保存，还能导出 PNG。每一步只加一个功能，所以你在任何一步停下来，代码都能运行。

如果你用 React，也建议先读这篇。[React 指南](https://fabricjs-document-engine.jscrate.dev/zh/docs/frameworks/react)用的是同样的引擎调用。

## 1. 页面

先准备一个画布和几个按钮：

```html
<canvas id="editor"></canvas>
<div class="toolbar">
  <button id="add">Add square</button>
  <button id="undo">Undo</button>
  <button id="redo">Redo</button>
  <button id="export">Export PNG</button>
  <span id="status" aria-live="polite"></span>
</div>
```

## 2. 画布和引擎

像平时一样创建 Fabric.js 画布，然后把引擎连上去：

```ts
import { Canvas, Rect } from "fabric";
import { createDocumentEngine } from "fabricjs-document-engine";
import { createLocalStorage } from "fabricjs-document-engine/storage";

const canvas = new Canvas("editor", {
  width: 800,
  height: 500,
  backgroundColor: "#ffffff",
});

const engine = createDocumentEngine({
  canvas,
  storage: createLocalStorage({ prefix: "tutorial:" }),
  autosave: { delay: 1000 },
  document: { id: "my-drawing" },
});
```

`storage` 告诉引擎文档存在哪里。`autosave` 会在用户停止编辑一秒后保存。

## 3. 添加图形

添加对象时 Fabric.js 会触发事件，所以引擎会自动把它记成一步撤销：

```ts
document.querySelector("#add")!.addEventListener("click", () => {
  canvas.add(
    new Rect({
      left: 50 + Math.random() * 600,
      top: 50 + Math.random() * 350,
      width: 80,
      height: 60,
      fill: "#e76f51",
    })
  );
});
```

## 4. 撤销和重做

把按钮连到 `undo()` 和 `redo()`，再用 `history:change` 事件让按钮状态保持同步：

```ts
import { bindKeyboardShortcuts } from "fabricjs-document-engine";

const undoButton = document.querySelector<HTMLButtonElement>("#undo")!;
const redoButton = document.querySelector<HTMLButtonElement>("#redo")!;

undoButton.addEventListener("click", () => engine.undo());
redoButton.addEventListener("click", () => engine.redo());

engine.on("history:change", ({ canUndo, canRedo, undoLabel }) => {
  undoButton.disabled = !canUndo;
  redoButton.disabled = !canRedo;
  undoButton.textContent = undoLabel ? `Undo ${undoLabel}` : "Undo";
});

bindKeyboardShortcuts(engine);
```

现在 Ctrl+Z 和 Cmd+Z 可以撤销，Ctrl+Shift+Z 可以重做。用鼠标移动或缩放图形，也各算一步。

## 5. 自动保存和保存状态

自动保存已经在运行了。把它的状态显示出来，并在页面加载时打开之前保存的画：

```ts
const status = document.querySelector("#status")!;

engine.on("save:status", ({ status: state }) => {
  status.textContent = state === "saved" ? "All changes saved" : state;
});

await engine.load("my-drawing").catch(() => {
  // Nothing saved yet: start with the empty document.
});
```

刷新页面，图形还在。

## 6. 导出 PNG

`export()` 会先检查图片和字体，再渲染。`downloadExport()` 负责触发下载：

```ts
import { downloadExport } from "fabricjs-document-engine";

document.querySelector("#export")!.addEventListener("click", async () => {
  const result = await engine.export({ format: "png", scale: 2 });
  downloadExport(result, "drawing.png");
});
```

## 你做出了什么

大约 60 行代码，这个 Fabric.js 使用教程里的画布编辑器就能：

- 添加图形，并撤销或重做每一步操作；
- 自动保存到 localStorage，刷新后重新打开；
- 以两倍尺寸导出 PNG。

## 下一步

- [保存冲突](https://fabricjs-document-engine.jscrate.dev/zh/docs/guides/save-conflicts)：两个标签页编辑同一份文档时会怎样。
- [恢复](https://fabricjs-document-engine.jscrate.dev/zh/docs/guides/recovery)：崩溃之后找回未保存的内容。
- [自定义后端](https://fabricjs-document-engine.jscrate.dev/zh/docs/storage/custom-backend)：保存到你自己的 API，而不是 localStorage。
