zaid
zaid

Reputation: 61

How to do undo redo in fabric js using enlivenObjects

The undo redo functionality works fine with this piece code but how to do the same with using fabricjs enlivenObjects method.I want the same results to implemented using enlivenObjects instead of using loadFromJSON method.the objects in the canvas objects container should be able to undo as well as redo.

<input type="button" value="addrect" onclick="addrect()">
<input type="button" value="undo" onclick="undo()">
<input type="button" value="redo" onclick="redo()">
<input type="button" value="clear" onclick="clearcan()">
<br/>
<canvas id="fabriccanvas" width="600" height="200" style="border:1px solid #ccc"></canvas>

var canvas = new fabric.Canvas('fabriccanvas');
canvas.counter = 0;
var newleft = 0;
canvas.selection = false;

addrect = function addrect(top, left, width, height, fill) {
    canvas.add(new fabric.Rect({
        top: document.getElementById("fabriccanvas").height,
        name: 'rectangle ' + window.counter,
        left: 0 + newleft,
        width: 100,
        height: 100,
        fill: '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6),
        //fix attributes applied for all rects
        opacity: 0.75,
        lockRotation: true,
        originX: 'left',
        originY: 'bottom',
        cornerSize: 15,
        hasRotatingPoint: false,
        perPixelTargetFind: true,
        minScaleLimit: 1
    }));
    updateModifications(true);
    canvas.counter++;
    newleft += 100;
}
var state = [];
var mods = 0;
canvas.on(
    'object:modified', function () {
    updateModifications(true);
},
    'object:added', function () {
    updateModifications(true);
});

function updateModifications(savehistory) {
    if (savehistory === true) {
        myjson = JSON.stringify(canvas);
        state.push(myjson);
    }
}

undo = function undo() {
    if (mods < state.length) {
        canvas.clear().renderAll();
        canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
        canvas.renderAll();
        //console.log("geladen " + (state.length-1-mods-1));
        //console.log("state " + state.length);
        mods += 1;
        //console.log("mods " + mods);
    }
}

redo = function redo() {
    if (mods > 0) {
        canvas.clear().renderAll();
        canvas.loadFromJSON(state[state.length - 1 - mods + 1]);
        canvas.renderAll();
        //console.log("geladen " + (state.length-1-mods+1));
        mods -= 1;
        //console.log("state " + state.length);
        //console.log("mods " + mods);
    }
}
clearcan = function clearcan() {
    canvas.clear().renderAll();
    newleft = 0;
}

Upvotes: 0

Views: 2725

Answers (1)

Marius Turcu
Marius Turcu

Reputation: 1511

I think is not such a good to use loadFromJSON or enlivenObjects to implement this. If you will have a large number of svgs loaded when you run undo/redo it will take some time to render, also doesn't make to much sense to reload everything for a small change (like position of a object). My ideea is to use two stacks where you will save the modifications of objects using fabric events. You can take a look here:

fabric.StaticCanvas.prototype.getObjectByName  = function(name){
  if(!name || typeof name === 'undefined'){
  return [];
  }
  return this._objects.filter(function(o) {
      return o.name === name;
    });
}
var canvas = new fabric.Canvas('fabriccanvas');

canvas.counter = 0;
var newleft = 0;
canvas.selection = false;
var undoStack = [];
var redoStack = [];
addrect = function addrect(top, left, width, height, fill) {
    var rect = new fabric.Rect({
        top: document.getElementById("fabriccanvas").height,
        name: 'rectangle ' + canvas.counter,
        left: 0 + newleft,
        width: 100,
        height: 100,
        fill: '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6),
        //fix attributes applied for all rects
        opacity: 0.75,
        lockRotation: true,
        originX: 'left',
        originY: 'bottom',
        cornerSize: 15,
        hasRotatingPoint: false,
        perPixelTargetFind: true,
        minScaleLimit: 1
    })
    canvas.add(rect);
    undoStack.push({
     type:'added',
     object : rect
    })
    canvas.counter++;
    newleft += 100;
    redoStack=[];
}
var state = [];
var mods = 0;
var props = {};
canvas.on(
    'mouse:down', function (e) {
    var block = e.target;
    if(block){
    	 props.oldStage = {
       left:block.left,
       top:block.top,
       width:block.width,
       height:block.height,
       scaleX:block.scaleX,
       scaleY:block.scaleY,
       }
    }
}).on(
    'mouse:up', function (e) {
    var block = e.target;
    if(block){
    	 props.newStage = {
       left:block.left,
       top:block.top,
       width:block.width,
       height:block.height,
       scaleX:block.scaleX,
       scaleY:block.scaleY,
       }
       undoStack.push({
        objectName : block.name,
        type:'modified',
        oldStage:props.oldStage,
        newStage:props.newStage
       });
       props={};
    }
});



undo = function undo() {
    if(undoStack.length){
     var undoData = undoStack.pop();
     if(undoData && undoData.type){
       switch(undoData.type){
         case 'added':
         var objectByName = canvas.getObjectByName(undoData.object.name);
         if(objectByName.length){
           canvas.remove(objectByName[0]);
          
         }
         break;
         case 'modified':
         var objectByName = canvas.getObjectByName(undoData.objectName);
         if(objectByName.length){
          	for(var key in undoData.oldStage){
              objectByName[0].set(key, undoData.oldStage[key]);
            }
         }
         break;
       }
       canvas.renderAll();
     }
     redoStack.push(undoData);
    }
}

redo = function redo() {
    if(redoStack.length){
     var redoData = redoStack.pop();
     if(redoData && redoData.type){
       switch(redoData.type){
         case 'added':
         if(redoData.object){
           canvas.add(redoData.object);
          
         }
         break;
         case 'modified':
         var objectByName = canvas.getObjectByName(redoData.objectName);
         if(objectByName.length){
          	for(var key in redoData.newStage){
              objectByName[0].set(key, redoData.newStage[key]);
            }
         }
         break;
       }
       canvas.renderAll();
     }
     undoStack.push(redoData);
    }
}
clearcan = function clearcan() {
    canvas.clear().renderAll();
    canvas.counter=0;
    undoStack=[];
    redoStack=[];
    newleft = 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.js"></script>
<input type="button" value="addrect" onclick="addrect()">
<input type="button" value="undo" onclick="undo()">
<input type="button" value="redo" onclick="redo()">
<input type="button" value="clear" onclick="clearcan()">
<br/>
<canvas id="fabriccanvas" width="600" height="200" style="border:1px solid #ccc"></canvas>

Upvotes: 1

Related Questions