Fabric.js Document Engine

Search documentation

Find a page or section

中文

Editor tutorial

View as Markdown

Build a small canvas editor from an empty page, one step at a time.

In this Fabric.js editor tutorial you build a small canvas editor with plain TypeScript. It adds shapes, supports undo redo, saves on its own and exports a PNG. Each step adds one feature, so you can stop at any point and still have working code.

If you use React, read this first anyway. The React guide uses the same engine calls.

1. The page

Start with a canvas and a few buttons:

<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. The canvas and the engine

Create the Fabric.js canvas as usual, then connect the engine to it:

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 tells the engine where documents live. autosave saves one second after the user stops editing.

3. Add shapes

Fabric.js fires an event when an object is added, so the engine records it as an undo step on its own:

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 and redo

Wire the buttons to undo() and redo(), and keep them in sync with the history:change event:

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);

Now Ctrl+Z and Cmd+Z undo, and Ctrl+Shift+Z redoes. Moving or resizing a shape with the mouse is one undo step too.

5. Autosave and the save status

Autosave already runs. Show its state, and open the saved drawing when the page loads:

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.
});

Refresh the page. Your shapes are still there.

6. Export a PNG

export() checks images and fonts first, then renders. downloadExport() starts the download:

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");
});

What you built

In about 60 lines, the canvas editor from this Fabric.js editor tutorial can:

  • add shapes and undo or redo every action,
  • autosave to localStorage and reopen after a refresh,
  • export PNG at twice the size.

Next steps