Implementing Robust Undo/Redo in Fabric.js Canvas Editors
This article explains how to add undo and redo functionality to a Fabric.js canvas editor by maintaining a history stack, provides full source code, discusses performance considerations, and compares alternative approaches such as the Command Pattern and object‑level diff snapshots.
Any editor needs undo and redo features, and this guide shows how to implement them in a Fabric.js image editor that supports various canvas transformations.
The example adds random‑colored rectangles and demonstrates undo/redo, which applies to all Fabric.js canvas applications.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fabric.js canvas undo and redo functionality</title>
</head>
<body>
<h1>Fabric.js 6.0.2 Canvas undo and redo</h1>
<input type="button" id="addrect" value="addrect" />
<input type="button" disabled="disabled" id="btnUndo" value="undo" />
<input type="button" disabled="disabled" id="btnRedo" value="redo" />
<input type="button" id="clear" value="clear" />
<canvas id="c" width="600" height="200" style="border:1px solid #ccc"></canvas>
<script src="assets/js/thirdparty/fabric.min.js"></script>
<script type='module'>
import UndoRedo from './UndoRedo.js';
var canvas = new fabric.Canvas('c');
canvas.counter = 0;
const ur = new UndoRedo(canvas);
var newleft = 0;
canvas.selection = false;
const Rectangle = {
addrect: function() {
ur.updateState();
canvas.add(new fabric.Rect({
top: canvas.height,
name: 'rectangle ' + window.counter,
left: 0 + newleft,
width: 100,
height: 100,
fill: '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6),
opacity: 0.75,
lockRotation: true,
originX: 'left',
originY: 'bottom',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1
}));
canvas.counter++;
newleft += 100;
},
clearcan: function() {
newleft = 0;
canvas.clear();
canvas.renderAll();
}
};
document.getElementById('addrect').addEventListener('click', function() {
Rectangle.addrect();
});
document.getElementById('clear').addEventListener('click', function() {
Rectangle.clearcan();
ur.clearAll();
});
</script>
</body>
</html>The editor records each operation by pushing the current canvas state (as JSON) onto an undo stack; undo pops the last state, pushes the current state onto a redo stack, and restores the popped state, while redo performs the inverse.
This stack‑based approach works for simple and complex edits, ensuring accurate and consistent undo/redo, but can consume significant memory and affect performance with many operations.
Alternative strategies include:
Command Pattern
Pros: minimal memory use, precise granularity, composable commands, O(1) state switches.
Cons: requires a command class for each action, deep knowledge of Fabric object structures, difficult to restore some complex operations.
Best for: advanced editors and systems needing fine‑grained control.
Object‑level Diff Snapshots
Pros: less space than full JSON, fast restoration, no custom command classes needed.
Cons: must track unique object IDs, intercept Fabric events, performance may degrade with many changes.
Best for: canvases with many objects but few changes, such as designers or annotation tools.
Summary
Undo and redo are essential for image editors; a well‑designed implementation like Photoveda's provides a solid reference for developers building similar functionality.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
