Функциональность отмены повтора отлично работает с этим частичным кодом, но как сделать то же самое с использованием метода enlivenObjects fabricjs. Я хочу, чтобы те же результаты были реализованы с использованием enlivenObjects вместо использования метода loadFromJSON. Объекты в контейнере объектов холста должны иметь возможность отменять как ну как повторить.
<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.info("geladen " + (state.length-1-mods-1));
//console.info("state " + state.length);
mods += 1;
//console.info("mods " + mods);
}
}
redo = function redo() {
if (mods > 0) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods + 1]);
canvas.renderAll();
//console.info("geladen " + (state.length-1-mods+1));
mods -= 1;
//console.info("state " + state.length);
//console.info("mods " + mods);
}
}
clearcan = function clearcan() {
canvas.clear().renderAll();
newleft = 0;
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я думаю, не очень хорошо использовать loadFromJSON или enlivenObjects для реализации этого.
Если у вас будет загружено большое количество SVG при выполнении отмены / повтора, потребуется некоторое время для рендеринга, также не имеет большого смысла перезагружать все для небольшого изменения (например, положения объекта).
Моя идея - использовать два стека, в которых вы будете сохранять модификации объектов с помощью событий ткани.
Вы можете посмотреть здесь:
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>