I'm trying to add undo/redo functionality to my Fabric.js canvas. My idea is to have a counter which counts canvas modifications (right now it counts the addition of objects). I have a state array, which pushes the whole canvas as JSON to my array.
Then I simply want to recall the states with
canvas.loadFromJSON(state[state.length - 1 + ctr],
As the user clicks on undo, ctr will reduce by one and load the state out of the array; as the user clicks on redo, ctr will increase by one and load the state out of the array.
When I experience this with simple numbers, everything works fine. With the real fabric canvas, I get some troubles --> it doesnt really work. I think this relies on my event handler
canvas.on({
'object:added': countmods
});
jsfiddle is here:
here is the working numbers only example (results see console): jsFiddle
I answered this on my own.
See jsfiddle:
What I did:
if (savehistory === true) {
myjson = JSON.stringify(canvas);
state.push(myjson);
} // this will save the history of all modifications into the state array, if enabled
if (mods < state.length) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
canvas.renderAll();
mods += 1;
} // this will execute the undo and increase a modifications variable so we know where we are currently. Vice versa works the redo function.
Would still need an improvement to handle both drawings and objects. But that should be simple.
You can use something like diff-patch
or tracking object version
. First, you listen to all object changes: object:created, object:modified...., save first snapshot of canvas by saving canvas.toObject() in a variable; For the next time, run diffpatcher.diff(snapshot,canvas.toObject()), and save only the patch. To undo, you can use diffpatcher.reverse these patch. To redo, just use function diffpatcher.patch. With this way, you can save memory, but cost more CPU usage.
With fabricjs you can use Object#saveState() and handling object:added to save original state to array(for undoing task), listening to object:modified, object:removing(for redoing task). This way is more lightweight and quite easy to implement. moreIt'd better to limit your history length by using circle queue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With