简体   繁体   中英

Undo and Redo Canvas Not Working as Expected

I have a canvas with an addable objects, as well as Undo and Redo buttons. As you can see in my example, I'm able to undo/redo 1 time but things break; by this I mean I can add an object and remove it but if for example I move the added object and hit undo, it should move to where I had it previously but instead it disappears from the canvas.

I'm using fabric.js 1.7.22.

My Code:

 var canvas = this.__canvas = new fabric.Canvas('canvas', { backgroundColor: 'grey', centeredScaling: true }); canvas.setWidth(400); canvas.setHeight(600); canvas. preserveObjectStacking = true; // Add Text function Addtext() { var text = new fabric.IText("Tape and Type...", { fontSize: 30, top: 10, left: 10, textAlign: "center", }); canvas.add(text); canvas.centerObject(text); canvas.setActiveObject(text); text.enterEditing(); text.selectAll(); canvas.renderAll(); canvas.isDrawingMode = false; } // Undo Redo canvas.on('object:added',function(){ if(!isRedoing){ h = []; } isRedoing = false; }); var isRedoing = false; var h = []; function undo(){ if(canvas._objects.length>0){ h.push(canvas._objects.pop()); canvas.renderAll(); } } function redo(){ if(h.length>0){ isRedoing = true; canvas.add(h.pop()); } } 
 <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <a href="#" class="btn btn-dark" onclick="Addtext()">Add Text</a> <button onclick="undo()" type="button" class="btn btn-sm btn-dark"> <i class="material-icons">undo</i> </button> <button onclick="redo()" type="button" class="btn btn-sm btn-dark"> <i class="material-icons">redo</i> </button> <canvas id="canvas"></canvas> 

You need to add some kind of state management functions that handle the state of the canvas, and that is able to restore and to update the state each time a change occurs.

The changes could be triggered by either the adding or updating of the canvas (the object:added , object:modified handlers on the canvas take care of this) or by the undo , redo actions.

To avoid those undo , redo actions colliding with the history and adding duplicates you need to flag them when they are happening, and that is where the canvas.loadFromJSON callback comes in handy to actually trigger an action after the canvas has been updated.

I added a functional example of it, and also added a couple of debugging messages so that the code is a little more understandable. Mostly is a little dense to read until you get used to it)

 const canvas = new fabric.Canvas('canvas', { backgroundColor: 'grey', centeredScaling: true }); canvas.setWidth(400); canvas.setHeight(600); canvas.preserveObjectStacking = true; var Addtext = () => { const text = new fabric.IText('Tape and Type...', { fontSize: 30, top: 10, left: 10, textAlign: 'center', }); canvas.add(text); canvas.centerObject(text); canvas.setActiveObject(text); text.enterEditing(); text.selectAll(); canvas.renderAll(); canvas.isDrawingMode = false; }; var canvasHistory = { state: [], currentStateIndex: -1, undoStatus: false, redoStatus: false, undoFinishedStatus: true, redoFinishedStatus: true, }; const updateHistory = () => { if (canvasHistory.undoStatus === true || canvasHistory.redoStatus === true) { console.log('Do not do anything, this got triggered automatically while the undo and redo actions were performed'); } else { const jsonData = canvas.toJSON(); const canvasAsJson = JSON.stringify(jsonData); // NOTE: This is to replace the canvasHistory when it gets rewritten 20180912:Alevale if (canvasHistory.currentStateIndex < canvasHistory.state.length - 1) { const indexToBeInserted = canvasHistory.currentStateIndex + 1; canvasHistory.state[indexToBeInserted] = canvasAsJson; const elementsToKeep = indexToBeInserted + 1; console.log(`History rewritten, preserved ${elementsToKeep} items`); canvasHistory.state = canvasHistory.state.splice(0, elementsToKeep); // NOTE: This happens when there is a new item pushed to the canvasHistory (normal case) 20180912:Alevale } else { console.log('push to canvasHistory'); canvasHistory.state.push(canvasAsJson); } canvasHistory.currentStateIndex = canvasHistory.state.length - 1; } }; canvas.on('object:added', () => { updateHistory(); }); canvas.on('object:modified', () => { updateHistory(); }); var undo = () => { if (canvasHistory.currentStateIndex - 1 === -1) { console.log('do not do anything anymore, you are going far to the past, before creation, there was nothing'); return; } if (canvasHistory.undoFinishedStatus) { canvasHistory.undoFinishedStatus = false; canvasHistory.undoStatus = true; canvas.loadFromJSON(canvasHistory.state[canvasHistory.currentStateIndex - 1], () => { canvas.renderAll(); canvasHistory.undoStatus = false; canvasHistory.currentStateIndex--; canvasHistory.undoFinishedStatus = true; }); } }; var redo = () => { if (canvasHistory.currentStateIndex + 1 === canvasHistory.state.length) { console.log('do not do anything anymore, you do not know what is after the present, do not mess with the future'); return; } if (canvasHistory.redoFinishedStatus) { canvasHistory.redoFinishedStatus = false; canvasHistory.redoStatus = true; canvas.loadFromJSON(canvasHistory.state[canvasHistory.currentStateIndex + 1], () => { canvas.renderAll(); canvasHistory.redoStatus = false; canvasHistory.currentStateIndex++; canvasHistory.redoFinishedStatus = true; }); } }; 
 <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <a href="#" class="btn btn-dark" onclick="Addtext()">Add Text</a> <button onclick="undo()" type="button" class="btn btn-sm btn-dark"> <i class="material-icons">undo</i> </button> <button onclick="redo()" type="button" class="btn btn-sm btn-dark"> <i class="material-icons">redo</i> </button> <canvas id="canvas"></canvas> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM