Undo-Redo feature in Fabric.js

前端 未结 9 741
梦谈多话
梦谈多话 2020-12-04 13:28

Is there any built-in support for for undo/redo in Fabric.js? Can you please guide me on how you used this cancel and repeat in [http://printio.ru/][1]

9条回答
  •  情话喂你
    2020-12-04 14:09

    Here is a solution that started with this simpler answer to the similar question, Undo Redo History for Canvas FabricJs.

    My answer is along the same lines as Tom's answer and the other answers that are modifications of Tom's answer.

    To track the state, I'm using JSON.stringify(canvas) and canvas.loadFromJSON() like the other answers and have an event registered on the object:modified to capture the state.

    One important thing is that the final canvas.renderAll() should be called in a callback passed to the second parameter of loadFromJSON(), like this

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

    This is because it can take a few milliseconds to parse and load the JSON and you need to wait until that's done before you render. It's also important to disable the undo and redo buttons as soon as they're clicked and to only re-enable in the same call back. Something like this

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

    I have an undo and a redo stack and a global for the last unaltered state. When some modification occurs, then the previous state is pushed into the undo stack and the current state is re-captured.

    When the user wants to undo, then current state is pushed to the redo stack. Then I pop off the last undo and both set it to the current state and render it on the canvas.

    Likewise when the user wants to redo, the current state is pushed to the undo stack. Then I pop off the last redo and both set it to the current state and render it on the canvas.

    The Code

             // 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);
              })
            });
    
      
      
    
    
    
      
      
      
      
    

提交回复
热议问题