这篇 Fabric.js 使用教程会带你用 TypeScript 做一个小型画布编辑器。它可以添加图形、撤销重做、自动保存,还能导出 PNG。每一步只加一个功能,所以你在任何一步停下来,代码都能运行。
如果你用 React,也建议先读这篇。React 指南用的是同样的引擎调用。
1. 页面
先准备一个画布和几个按钮:
<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 画布,然后把引擎连上去:
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 会触发事件,所以引擎会自动把它记成一步撤销:
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 事件让按钮状态保持同步:
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. 自动保存和保存状态
自动保存已经在运行了。把它的状态显示出来,并在页面加载时打开之前保存的画:
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() 负责触发下载:
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。