Подтвердить что ты не робот

Отменить историю повтора для холста FabricJs

Я пытаюсь добавить функциональность отмены/повтора к моему холсту FabricJs. Моя идея - иметь счетчик, который учитывает модификации холста (прямо сейчас он подсчитывает добавление объектов). У меня есть массив состояний, который подталкивает весь холст как JSON к моему массиву.

Затем я просто хочу вспомнить состояния с

canvas.loadFromJSON(state[state.length - 1 + ctr],

Когда пользователь нажимает на отмену, ctr уменьшает на единицу и загружает состояние из массива; как пользователь нажимает на повтор, ctr будет увеличиваться на единицу и загружать состояние из массива.

Когда я испытываю это с помощью простых чисел, все работает нормально. С реальным холстом ткани, я получаю некоторые проблемы → он действительно не работает. Я думаю, что это зависит от моего обработчика событий

canvas.on({
   'object:added': countmods
});

jsfiddle находится здесь:

вот пример рабочих чисел (результаты см. в консоли): jsFiddle

4b9b3361

Ответ 1

Я ответил на это сам.

См. jsfiddle:

Что я сделал:

if (savehistory === true) {
    myjson = JSON.stringify(canvas);
    state.push(myjson);
} // this will save the history of all modifications into the state array, if enabled

if (mods < state.length) {
    canvas.clear().renderAll();
    canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
    canvas.renderAll();
    mods += 1;
} // this will execute the undo and increase a modifications variable so we know where we are currently. Vice versa works the redo function.

По-прежнему потребуется улучшение для обработки как чертежей, так и объектов. Но это должно быть просто.

Ответ 2

Сериализация всего холста в JSON может быть дорогостоящим, если на холсте много объектов. В общем, существует два подхода:

  • сохранение всего состояния (того, которое вы выбрали)
  • сохранение действий

Здесь можно читать .

Другой подход к реализации undo/redo - это шаблон команды, который может быть более эффективным. Для реализации посмотрите здесь, а также опыт других людей (состояние против действий) здесь

Здесь также большое понимание стратегии реализации.

Ответ 3

Вы можете использовать что-то вроде diff-patch или tracking object version. Во-первых, вы слушаете все изменения объекта: object: created, object: modified...., сохраняете первый снимок холста, сохраняя canvas.toObject() в переменной; В следующий раз запустите diffpatcher.diff(snapshot, canvas.toObject()) и сохраните только патч. Чтобы отменить, вы можете использовать diffpatcher.reverse эти патч. Для повторного использования просто используйте функцию diffpatcher.patch. Таким образом, вы можете сэкономить память, но стоить больше использования ЦП.

С помощью fabricjs вы можете использовать Object # saveState() и объект обработки: добавлено для сохранения исходного состояния в массив (для отмены задачи), прослушивания объекта: изменено, объект: удаление (для повторной задачи). Этот способ более легкий и довольно простой в использовании. moreIt'd лучше ограничить длину истории, используя кругную очередь.

Ответ 4

Важно отметить, что окончательный 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 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>

Ответ 5

Как говорят больщики, экономия всего государства дорого. Он будет "работать", но он не будет работать.

История вашего штата будет проходить с небольшими изменениями, и это ничего не говорит о хите производительности с необходимостью перерисовать весь холст с нуля каждый раз, когда вы отмените/повторите...

То, что я использовал в прошлом, и то, что я сейчас использую, - это шаблон команды. Я нашел эту (общую) библиотеку, чтобы помочь с ворчанием: https://github.com/strategydynamics/commandant

Просто начал реализовывать его, но пока он работает очень хорошо.

Чтобы обобщить шаблон команды в целом:

  • Вы хотите что-то сделать. ex: добавить слой к холсту
  • Создайте способ добавления слоя. ex: do {canvas.add(...)}
  • Создайте метод для удаления слоя. ex: undo {canvas.remove(...)}

Затем, когда вы хотите добавить слой. Вы вызываете команду вместо добавления слоя напрямую.

Очень легкий и хорошо работает.