【问题标题】:Undo-Redo feature in Fabric.jsFabric.js 中的撤消-重做功能
【发布时间】:2013-10-03 07:20:25
【问题描述】:

Fabric.js 中是否有对撤消/重做的内置支持?您能否指导我如何使用此取消并在 [http://printio.ru/][1]

中重复

【问题讨论】:

标签: fabricjs


【解决方案1】:

http://jsfiddle.net/SpgGV/9/ 中,移动对象并更改其大小。如果对象的状态发生了变化,然后我们做undo/redo,那么当下一次变化来临时,它之前的状态就会被删除。它使撤消/重做变得更容易。在将任何元素添加到画布之前,应调用画布的所有事件。我没有在这里添加object:remove 事件。你可以自己添加。如果删除了一个元素,则如果该元素在此数组中,则状态和列表应该是无效的。更简单的方法是设置statelist = []index = 0

这将清除您的撤消/重做队列的状态。如果您想保留所有状态,例如添加/删除,我的建议是为您的状态数组的元素添加更多属性。例如,state = [{"data":object.originalState, "event": "added"}, ....]。 “事件”可以被“修改”或“添加”并设置在相应的事件处理程序中。

如果您添加了一个对象,则设置state[index].event="added",以便下次使用撤消时检查它。如果它是“添加”的,那么无论如何都要删除它。或者当您使用重做时,如果目标是“添加”的,那么您添加了它。我最近很忙。稍后我会在 jsfiddle.net 中添加代码。

更新:添加了setCoords()

var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;

canvas.on("object:added", function (e) {
    var object = e.target;
    console.log('object:modified');

    if (action === true) {
        state = [state[index2]];
        list = [list[index2]];

        action = false;
        console.log(state);
        index = 1;
    }
    object.saveState();

    console.log(object.originalState);
    state[index] = JSON.stringify(object.originalState);
    list[index] = object;
    index++;
    index2 = index - 1;



    refresh = true;
});

canvas.on("object:modified", function (e) {
    var object = e.target;
    console.log('object:modified');

    if (action === true) {
        state = [state[index2]];
        list = [list[index2]];

        action = false;
        console.log(state);
        index = 1;
    }

    object.saveState();

    state[index] = JSON.stringify(object.originalState);
    list[index] = object;
    index++;
    index2 = index - 1;

    console.log(state);
    refresh = true;
});

function undo() {

    if (index <= 0) {
        index = 0;
        return;
    }

    if (refresh === true) {
        index--;
        refresh = false;
    }

    console.log('undo');

    index2 = index - 1;
    current = list[index2];
    current.setOptions(JSON.parse(state[index2]));

    index--;
    current.setCoords();
    canvas.renderAll();
    action = true;
}

function redo() {

    action = true;
    if (index >= state.length - 1) {
        return;
    }

    console.log('redo');

    index2 = index + 1;
    current = list[index2];
    current.setOptions(JSON.parse(state[index2]));

    index++;
    current.setCoords();
    canvas.renderAll();
}

更新:将编辑历史算法考虑在内的更好解决方案。这里我们可以使用Editing.getInst().set(item),其中项目可以是{action, object, state};例如,{"add", object, "{JSON....}"}

/**
 * Editing : we will save element states into an queue, and the length of queue 
 * is fixed amount, for example, 0..99, each element will be insert into the top 
 * of queue, queue.push, and when the queue is full, we will shift the queue, 
 * to remove the oldest element from the queue, queue.shift, and then we will 
 * do push. 
 * 
 * So the latest state will be at the top of queue, and the oldest one will be 
 * at the bottom of the queue (0), and the top of queue is changed, could be 
 * 1..99.
 * 
 * The initialized action is "set", it will insert item into the top of queue,
 * even if it arrived the length of queue, it will queue.shift, but still do
 * the same thing, and queue only abandon the oldest element this time. When
 * the current is changed and new state is coming, then this time, top will be
 * current + 1.
 *
 * The prev action is to fetch "previous state" of the element, and it will use
 * "current" to do this job, first, we will --current, and then we will return
 * the item of it, because "current" always represent the "current state" of
 * element. When the current is equal 0, that means, we have fetched the last
 * element of the queue, and then it arrived at the bottom of the queue.
 *
 * The next action is to fetch "next state" after current element, and it will
 * use "current++" to do the job, when the current is equal to "top", it means
 * we have fetched the latest element, so we should stop.
 *
 * If the action changed from prev/next to "set", then we should reset top to
 * "current", and abandon all rest after that...
 *
 * Here we should know that, if we keep the reference in the queue, the item
 * in the queue will never be released.
 *
 *
 * @constructor
 */
function Editing() {

    this.queue = [];
    this.length = 4;
    this.bottom = 0;
    this.top = 0;
    this.current = 0;
    this.empty = true;

    // At the Begin of Queue
    this.BOQ = true;

    // At the End of Queue
    this.EOQ = true;

    // 0: set, 1: prev, 2: next
    this._action = 0;
    this._round = 0;
}

Editing.sharedInst = null;
Editing.getInst = function (owner) {

    if (Editing.sharedInst === null) {
        Editing.sharedInst = new Editing(owner);
    }

    return Editing.sharedInst;
};

/**
 * To set the item into the editing queue, and mark the EOQ, BOQ, so we know
 * the current position.
 *
 * @param item
 */
Editing.prototype.set = function (item) {

    console.log("=== Editing.set");

    var result = null;

    if (this._action != 0) {
        this.top = this.current + 1;
    }

    if (this.top >= this.length) {
        result = this.queue.shift();
        this.top = this.length - 1;
    }

    this._action = 0;
    this.queue[this.top] = item;
    this.current = this.top;
    this.top++;

    this.empty = false;
    this.EOQ = true;
    this.BOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return result;

};

/**
 * To fetch the previous item just before current one
 *
 * @returns {item|boolean}
 */
Editing.prototype.prev = function () {

    console.log("=== Editing.prev");

    if (this.empty) {
        return false;
    }

    if (this.BOQ) {
        return false;
    }

    this._action = 1;

    this.current--;

    if (this.current == this.bottom) {
        this.BOQ = true;
    }

    var item = this.queue[this.current];
    this.EOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return item;
};

/**
 * To fetch the next item just after the current one
 *
 * @returns {*|boolean}
 */
Editing.prototype.next = function () {

    console.log("=== Editing.next");

    if (this.empty) {
        return false;
    }

    if (this.EOQ) {
        return false;
    }

    this.current++;

    if (this.current == this.top - 1 && this.top < this.length) {
        this.EOQ = true;
    }

    if (this.current == this.top - 1 && this.top == this.length) {
        this.EOQ = true;
    }

    this._action = 2;

    var item = this.queue[this.current];
    this.BOQ = false;

    console.log("==> INFO : ");
    console.log(item);
    console.log("===========");
    console.log("current: ", 0 + this.current);
    console.log("start: ", 0 + this.bottom);
    console.log("end: ", 0 + this.top);

    return item;
};


/**
 * To empty the editing and reset all state
 */
Editing.prototype.clear = function () {

    this.queue = [];
    this.bottom = 0;
    this.top = 0;
    this.current = 0;
    this.empty = true;
    this.BOQ = true;
    this.EOQ = false;
};

【讨论】:

  • 感谢您的帮助。试过了。但是我收到一个错误:Uncaught TypeError: Cannot set property 'stateProperties' of undefined。这是小提琴:jsfiddle.net/xadqg/14
  • 人绝对是我的错,我只是把这些代码放在脑海中,从未在实际中测试过,而这个jsfiddle.net/hellomaya/SpgGV/2,这还不完美,请根据你的想法进行修改。
  • 添加“current.addCoords()”以避免在fabric.js新版本中放错地方
  • 抱歉,但 jsfiddle 似乎不再做太多事情了……我看到一个白色矩形,下面有 4 个按钮。点击按钮没有任何作用
  • 这似乎并没有撤消对象创建。也可以这样吗?
【解决方案2】:

这是一个解决方案,从这个更简单的answer 开始,到类似的问题Undo Redo History for Canvas FabricJs

我的答案与 Tom's answerother answers 相同,它们是汤姆答案的修改。

为了跟踪状态,我像其他答案一样使用JSON.stringify(canvas)canvas.loadFromJSON(),并在object:modified 上注册了一个事件来捕获状态。

一个重要的事情是最终的canvas.renderAll()应该在传递给loadFromJSON()的第二个参数的回调中调用,像这样

canvas.loadFromJSON(state, function() {
    canvas.renderAll();
}

这是因为解析和加载 JSON 可能需要几毫秒的时间,您需要等到完成后才能进行渲染。一旦单击撤消和重做按钮并仅在同一个回调中重新启用,也很重要。像这样的

$('#undo').prop('disabled', true);
$('#redo').prop('disabled', true);    
canvas.loadFromJSON(state, function() {
    canvas.renderAll();
    // now turn buttons back on appropriately
    ...
    (see full code below)
}

我有一个撤消和一个重做堆栈以及最后一个未更改状态的全局。当一些修改发生时,之前的状态被压入撤消堆栈并重新捕获当前状态。

当用户想要撤消时,当前状态被推送到重做堆栈。然后我弹出最后一个撤消并将其设置为当前状态并将其呈现在画布上。

同样,当用户想要重做时,当前状态会被推送到撤消堆栈。然后我弹出最后一个重做并将其设置为当前状态并将其呈现在画布上。

守则

         // Fabric.js Canvas object
        var canvas;
         // current unsaved state
        var state;
         // past states
        var undo = [];
         // reverted states
        var redo = [];

        /**
         * Push the current state into the undo stack and then capture the current state
         */
        function save() {
          // clear the redo stack
          redo = [];
          $('#redo').prop('disabled', true);
          // initial call won't have a state
          if (state) {
            undo.push(state);
            $('#undo').prop('disabled', false);
          }
          state = JSON.stringify(canvas);
        }

        /**
         * Save the current state in the redo stack, reset to a state in the undo stack, and enable the buttons accordingly.
         * Or, do the opposite (redo vs. undo)
         * @param playStack which stack to get the last state from and to then render the canvas as
         * @param saveStack which stack to push current state into
         * @param buttonsOn jQuery selector. Enable these buttons.
         * @param buttonsOff jQuery selector. Disable these buttons.
         */
        function replay(playStack, saveStack, buttonsOn, buttonsOff) {
          saveStack.push(state);
          state = playStack.pop();
          var on = $(buttonsOn);
          var off = $(buttonsOff);
          // turn both buttons off for the moment to prevent rapid clicking
          on.prop('disabled', true);
          off.prop('disabled', true);
          canvas.clear();
          canvas.loadFromJSON(state, function() {
            canvas.renderAll();
            // now turn the buttons back on if applicable
            on.prop('disabled', false);
            if (playStack.length) {
              off.prop('disabled', false);
            }
          });
        }

        $(function() {
          ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
          // Set up the canvas
          canvas = new fabric.Canvas('canvas');
          canvas.setWidth(500);
          canvas.setHeight(500);
          // save initial state
          save();
          // register event listener for user's actions
          canvas.on('object:modified', function() {
            save();
          });
          // draw button
          $('#draw').click(function() {
            var imgObj = new fabric.Circle({
              fill: '#' + Math.floor(Math.random() * 16777215).toString(16),
              radius: Math.random() * 250,
              left: Math.random() * 250,
              top: Math.random() * 250
            });
            canvas.add(imgObj);
            canvas.renderAll();
            save();
          });
          // undo and redo buttons
          $('#undo').click(function() {
            replay(undo, redo, '#redo', this);
          });
          $('#redo').click(function() {
            replay(redo, undo, '#undo', this);
          })
        });
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" type="text/javascript"></script>
</head>

<body>
  <button id="draw">circle</button>
  <button id="undo" disabled>undo</button>
  <button id="redo" disabled>redo</button>
  <canvas id="canvas" style="border: solid 1px black;"></canvas>
</body>

【讨论】:

  • 这对我来说几乎是完美的。但是,第一次撤消按总是会清除我的 Canvas 的图像?
【解决方案3】:

我允许用户删除最后添加的路径(在我的绘画应用程序中),这对我来说很好:

var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);

if(item.get('type') === 'path') {
  fabricCanvas.remove(item);
  fabricCanvas.renderAll();
}

但您也可以删除 IF 语句并让人们删除任何内容。

【讨论】:

  • 至少在免费绘图方面效果更好!
【解决方案4】:

我知道回答这个问题已经很晚了,但这是我的实现版本。可能对某人有用。

我通过将 Canvas 状态保存为 JSON 来实现此功能。每当用户在Canvas 中添加或修改对象时,它将保存更改的画布状态并将其维护在array 中。然后,每当用户单击撤消或重做按钮时,都会操纵此 array

看看这个链接。我还提供了一个有效的演示 URL。

https://github.com/abhi06991/Undo-Redo-Fabricjs

HTML:

<canvas id="canvas" width="400" height="400"></canvas> 
<button type="button" id="undo" >Undo</button>
<button type="button" id="redo" disabled>Redo</button>

JS:

var canvasDemo = (function(){
  var _canvasObject = new fabric.Canvas('canvas',{backgroundColor : "#f5deb3"});
    var _config = {
        canvasState             : [],
        currentStateIndex       : -1,
        undoStatus              : false,
        redoStatus              : false,
        undoFinishedStatus      : 1,
        redoFinishedStatus      : 1,
    undoButton              : document.getElementById('undo'),
        redoButton              : document.getElementById('redo'),
    };
    _canvasObject.on(
        'object:modified', function(){
            updateCanvasState();
        }
    );

  _canvasObject.on(
        'object:added', function(){
            updateCanvasState();
        }
    );

  var addObject = function(){
     var rect = new fabric.Rect({
            left   : 100,
            top    : 100,
            fill   : 'red',
            width  : 200,
            height : 200
    });
        _canvasObject.add(rect);
        _canvasObject.setActiveObject(rect);
    _canvasObject.renderAll();
  }

    var updateCanvasState = function() {
        if((_config.undoStatus == false && _config.redoStatus == false)){
            var jsonData        = _canvasObject.toJSON();
            var canvasAsJson        = JSON.stringify(jsonData);
            if(_config.currentStateIndex < _config.canvasState.length-1){
                var indexToBeInserted                  = _config.currentStateIndex+1;
                _config.canvasState[indexToBeInserted] = canvasAsJson;
                var numberOfElementsToRetain           = indexToBeInserted+1;
                _config.canvasState                    = _config.canvasState.splice(0,numberOfElementsToRetain);
            }else{
            _config.canvasState.push(canvasAsJson);
            }
        _config.currentStateIndex = _config.canvasState.length-1;
      if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
        _config.redoButton.disabled= "disabled";
      }
        }
    }


    var undo = function() {
        if(_config.undoFinishedStatus){
            if(_config.currentStateIndex == -1){
            _config.undoStatus = false;
            }
            else{
            if (_config.canvasState.length >= 1) {
            _config.undoFinishedStatus = 0;
              if(_config.currentStateIndex != 0){
                    _config.undoStatus = true;
                  _canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex-1],function(){
                                var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex-1]);
                            _canvasObject.renderAll();
                        _config.undoStatus = false;
                        _config.currentStateIndex -= 1;
                                _config.undoButton.removeAttribute("disabled");
                                if(_config.currentStateIndex !== _config.canvasState.length-1){
                                    _config.redoButton.removeAttribute('disabled');
                                }
                            _config.undoFinishedStatus = 1;
                });
              }
              else if(_config.currentStateIndex == 0){
                _canvasObject.clear();
                        _config.undoFinishedStatus = 1;
                        _config.undoButton.disabled= "disabled";
                        _config.redoButton.removeAttribute('disabled');
                _config.currentStateIndex -= 1;
              }
            }
            }
        }
    }

    var redo = function() {
        if(_config.redoFinishedStatus){
            if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
                _config.redoButton.disabled= "disabled";
            }else{
            if (_config.canvasState.length > _config.currentStateIndex && _config.canvasState.length != 0){
                    _config.redoFinishedStatus = 0;
                _config.redoStatus = true;
              _canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex+1],function(){
                            var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex+1]);
                        _canvasObject.renderAll();
                        _config.redoStatus = false;
                    _config.currentStateIndex += 1;
                            if(_config.currentStateIndex != -1){
                                _config.undoButton.removeAttribute('disabled');
                            }
                        _config.redoFinishedStatus = 1;
            if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
              _config.redoButton.disabled= "disabled";
            }
              });
            }
            }
        }
    }


    return {
        addObject  : addObject,
        undoButton : _config.undoButton,
        redoButton : _config.redoButton,
        undo       : undo,
        redo       : redo,
  }


  })();



  canvasDemo.undoButton.addEventListener('click',function(){
        canvasDemo.undo();
    });

    canvasDemo.redoButton.addEventListener('click',function(){
        canvasDemo.redo();
    });
  canvasDemo.addObject();

【讨论】:

  • 清理撤消最后一个对象,画布被完全清理。最后一个对象如何撤消? (它不应该 clear() 到画布上)
【解决方案5】:

我的用例是绘制类似于蓝图的简单形状,因此我不必担心保存整个画布状态的开销。如果您处于相同的情况,这很容易实现。此代码假定您在画布周围有一个“包装器”div,并且您希望将撤消/重做功能绑定到“CTRL+Z”和“CTRL+Y”的标准 Windows 击键。

“pause_saving”变量的目的是说明当重新渲染画布时,它似乎又一个一个地创建了每个对象,我们不想捕捉这些事件,因为它们并不是真正的新事件。

//variables for undo/redo
let pause_saving = false;
let undo_stack = []
let redo_stack = []

canvas.on('object:added', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object added, state saved', undo_stack);
    }

});
canvas.on('object:modified', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object modified, state saved', undo_stack);
    }
});
canvas.on('object:removed', function(event){
    if (!pause_saving) {
        undo_stack.push(JSON.stringify(canvas));
        redo_stack = [];
        console.log('Object removed, state saved', undo_stack);
    }
});

//Listen for undo/redo 
wrapper.addEventListener('keydown', function(event){
    //Undo - CTRL+Z
    if (event.ctrlKey && event.keyCode == 90) {
        pause_saving=true;
        redo_stack.push(undo_stack.pop());
        let previous_state = undo_stack[undo_stack.length-1];
        if (previous_state == null) {
            previous_state = '{}';
        }
        canvas.loadFromJSON(previous_state,function(){
            canvas.renderAll();
        })
        pause_saving=false;
    }
    //Redo - CTRL+Y
    else if (event.ctrlKey && event.keyCode == 89) {
        pause_saving=true;
        state = redo_stack.pop();
        if (state != null) {
            undo_stack.push(state);
            canvas.loadFromJSON(state,function(){
                canvas.renderAll();
            })
            pause_saving=false;
        }
    }
});

【讨论】:

    【解决方案6】:

    您可以为此使用“object:added”和/或“object:removed”——fabricjs.com/events

    你可以关注这个帖子: Do we have canvas Modified Event in Fabric.js?

    【讨论】:

    • object: added /object:removed 只是表示是否添加了对象。但我什至需要更改状态级别。例如,如果我添加一个对象缩放它旋转它平移它,现在撤消功能应该像撤消平移下一个撤消旋转下一个撤消缩放下一个撤消添加对象
    【解决方案7】:

    我知道答案已经被选中,但这是我的版本,脚本是精简的,还添加了重置为原始状态。在任何要保存的事件之后,只需调用 saveState(); jsFiddle

        canvas = new fabric.Canvas('canvas', {
            selection: false
        });
    function saveState(currentAction) {
        currentAction = currentAction || '';
        // if (currentAction !== '' && lastAction !== currentAction) {
            $(".redo").val($(".undo").val());
            $(".undo").val(JSON.stringify(canvas));
            console.log("Saving After " + currentAction);
            lastAction = currentAction;
        // }
        var objects = canvas.getObjects();
        for (i in objects) {
            if (objects.hasOwnProperty(i)) {
                objects[i].setCoords();
            }
        }
    }
    canvas.on('object:modified', function (e) {
       saveState("modified");
    });
    // Undo Canvas Change
    function undo() {
        canvas.loadFromJSON($(".redo").val(), canvas.renderAll.bind(canvas));
    }
    // Redo Canvas Change
    function redo() {
        canvas.loadFromJSON($(".undo").val(), canvas.renderAll.bind(canvas));
    };
    $("#reset").click(function () {
        canvas.loadFromJSON($("#original_canvas").val(),canvas.renderAll.bind(canvas));
    });
    
    var bgnd = new fabric.Image.fromURL('https://s3-eu-west-1.amazonaws.com/kienzle.dev.cors/img/image2.png', function(oImg){
        oImg.hasBorders = false;
        oImg.hasControls = false;
        // ... Modify other attributes
        canvas.insertAt(oImg,0);
        canvas.setActiveObject(oImg);
        myImg = canvas.getActiveObject();
        saveState("render");
        $("#original_canvas").val(JSON.stringify(canvas.toJSON()));
    });
    
    $("#undoButton").click(function () {
        undo();
    });
    $("#redoButton").click(function () {
        redo();
    });
    

    【讨论】:

    • 嗨,我用最新的 Fabric 测试过你,好像不行。
    【解决方案8】:

    我为你开发了一个小脚本,希望对你有帮助。看这个演示Fiddle 虽然重做并不完美,但您必须在撤消按钮上单击至少两次然后重做工作。您可以通过在重做代码中提供简单的条件轻松解决此问题。 //HTML

    <canvas id="c" width="400" height="200" style=" border-radius:25px 25px 25px 25px"></canvas>
      <br>
       <br>
     <input type="button" id="addtext" value="Add Text"/>
    <input type="button" id="undo" value="Undo"/>
    <input type="button" id="redo" value="redo"/>
    <input type="button" id="clear" value="Clear Canvas"/>   
    

    //脚本

      var canvas = new fabric.Canvas('c');
        var text = new fabric.Text('Sample', {
       fontFamily: 'Hoefler Text',
        left: 50,
       top: 30,
        //textAlign: 'center',
        fill: 'navy',
    
    });
    canvas.add(text);
    var vall=10;    
    var l=0;
    var flag=0;
    var k=1;
    var yourJSONString = new Array();
    canvas.observe('object:selected', function(e) {
        //yourJSONString = JSON.stringify(canvas);
        if(k!=10)
    {   
    yourJSONString[k] = JSON.stringify(canvas);
    k++;
    }
    j = k;
        var activeObject = canvas.getActiveObject();
        });
    $("#undo").click(function(){
         if(k-1!=0)
         {
         canvas.clear();
         canvas.loadFromJSON(yourJSONString[k-1]);
         k--;
         l++;
         } 
      canvas.renderAll();   
     });
    
    $("#redo").click(function(){
        if(l > 1)
         {  
          canvas.clear();
          canvas.loadFromJSON(yourJSONString[k+1]);
           k++;
           l--;
          canvas.renderAll();   
         }
     });
    
    $("#clear").click(function(){
        canvas.clear();
      });
     $("#addtext").click(function(){
    var text = new fabric.Text('Sample', {
       fontFamily: 'Hoefler Text',
        left: 100,
        top: 100,
        //textAlign: 'center',
        fill: 'navy',
    
    });
    canvas.add(text);
    }); 
    

    【讨论】:

    • 这个答案是来自@Tom 的一个分支。应该归功于汤姆。 jsfiddle 似乎不再做太多事情了……我看到一个白色的矩形,下面有 4 个按钮。点击按钮没有任何作用
    【解决方案9】:

    我已经回答了你所有的问题 :) 微笑 检查此链接..一切都完成了...复制并粘贴它:P http://jsfiddle.net/SpgGV/27/

    var canvas = new fabric.Canvas('c');
    var current;
    var list = [];
    var state = [];
    var index = 0;
    var index2 = 0;
    var action = false;
    var refresh = true;
    state[0] = JSON.stringify(canvas.toDatalessJSON());
    console.log(JSON.stringify(canvas.toDatalessJSON()));
    $("#clear").click(function(){
    canvas.clear();
    index=0;
    });
    $("#addtext").click(function(){
    ++index;
    action=true;  
    var text = new fabric.Text('Sample', {
    fontFamily: 'Hoefler Text',
    left: 100,
    top: 100,
    //textAlign: 'center',
    fill: 'navy',
    
    });
    canvas.add(text);   
    }); 
    canvas.on("object:added", function (e) {
    
    if(action===true){
    
    var object = e.target;
    console.log(JSON.stringify(canvas.toDatalessJSON()));
    state[index] = JSON.stringify(canvas.toDatalessJSON());
    refresh = true;
    action=false;
    canvas.renderAll();
    }
    });
    function undo() {
    
    if (index < 0) {
    index = 0;
    canvas.loadFromJSON(state[index]);
    canvas.renderAll();
    return;
    }
    
    
    console.log('undo');
    
    canvas.loadFromJSON(state[index]);
    
    console.log(JSON.stringify(canvas.toDatalessJSON()));
    canvas.renderAll();
    action = false;
    }
    
    function redo() {
    
    action = false;
    if (index >= state.length - 1) {
    canvas.loadFromJSON(state[index]);
    canvas.renderAll();
    return;
    }
    
    console.log('redo');
    
    canvas.loadFromJSON(state[index]);
    
    console.log(JSON.stringify(canvas.toDatalessJSON()));
    canvas.renderAll();
    
    canvas.renderAll();
    
    }
    
    canvas.on("object:modified", function (e) {
    var object = e.target;
    console.log('object:modified');
    console.log(JSON.stringify(canvas.toDatalessJSON()));
    state[++index] = JSON.stringify(canvas.toDatalessJSON());
    action=false;
    });
    
    $('#undo').click(function () {
    index--;
    undo();
    });
    $('#redo').click(function () {
    index++;
    redo();
    });
    

    【讨论】:

    • 当我添加图像并调用 undo /redo 时,您的功能 undo /redo 不起作用?例如。当我添加 2 个图像并说重做画布完全空白时,如果添加一些图像,则剩余的 1 个图像和文本变得可见
    • 当你添加对象和 sacle 时,它​​会旋转它——撤消的点击应该跟随旋转比例删除。但是这里的对象没有保存状态。问题只是当我添加图像时
    • 我已经尝试上传图片,然后正如你所说,我添加了 2 张图片,然后重做它..即使在那之后它也能正常工作..你的代码可能有问题..即使在之后做任何类型的转换事情都进展顺利:)
    • 点击后才显示图片
    • 这个答案是来自@Tom 的一个分支。应该归功于汤姆。 jsfiddle 似乎不再做太多事情了……我看到一个白色的矩形,下面有 4 个按钮。点击按钮没有任何作用
    猜你喜欢
    • 2014-04-15
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多