撤消和重做画布不按预期工作

时间:2018-02-23 14:25:36

标签: javascript fabricjs

我有一个带有可添加对象的画布,以及Undo和Redo按钮。正如你在我的例子中看到的那样,我能够撤消/重做一次,但事情就会破裂;通过这个我的意思是我可以添加一个对象并删除它,但是如果我移动添加的对象并点击撤消,它应该移动到我以前的位置,但它会从画布中消失。

我正在使用fabric.js 1.7.22。

我的代码:



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>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:7)

您需要添加某种状态管理功能来处理画布的状态,并能够在每次更改发生时恢复和更新状态。

可以通过添加或更新画布(画布上的object:addedobject:modified处理程序来处理)或由undo,{{ 1}}操作。

为避免这些redoundo动作与历史记录发生冲突并添加重复项,您需要在它们发生时对其进行标记,这就是redo回调很方便的地方在画布更新后实际上触发了一个动作。

我添加了一个功能示例,还添加了一些调试消息,以便使代码更易于理解。在您习惯之前,通常阅读起来有点密集)

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