Fabric JS - 使用 JSON diff 进行 UNDO 和 REDO 优化
Posted
技术标签:
【中文标题】Fabric JS - 使用 JSON diff 进行 UNDO 和 REDO 优化【英文标题】:Fabric JS - UNDO & REDO optimization using JSON diff 【发布时间】:2021-10-22 03:27:51 【问题描述】:目前,我通过使用侦听器触发canvas.getObjects()
,实现了相当标准的UNDO和REDO,我将其JSON输出存储在堆栈中。
// Canvas modified listeners
canvas?.on('object:modified', onCanvasModifiedHandler)
canvas?.on('object:removed', onCanvasModifiedHandler)
canvas?.on('object:changed', onCanvasModifiedHandler)
当用户单击撤消和重做时,我们从堆栈中获取画布的 JSON 表示并使用 canvas?.loadFromJSON(json, () => ... )
加载它
我的问题是,当实际变化很小时,存储画布的整个 JSON 表示是非常低效的。因此,当用户单击 UNDO 和 REDO 时,这种方法会导致我的应用程序冻结 500 毫秒。
我建议的解决方案是使用例如这个包仅存储 JSON 差异,尽管这是一项艰巨的任务。 https://www.npmjs.com/package/jsondiffpatch
我的问题是以前是否有人遇到过这个问题,在这种情况下您是如何解决的?或者如果有人有其他想法。
受此线程启发:https://bountify.co/undo-redo-with-2-canvases-in-fabric-js
【问题讨论】:
嗨。我同意,这是一项艰巨的任务。可以存储的不是整个历史记录,而是 10 或 20 条记录。 【参考方案1】:我认为您需要为此使用命令模式。这将比使用所有 JSON 数据更有效。为此,您需要实施下一种方法:
-
创建一个用于存储历史的类。它可能看起来像这样
class CommandHistory
commands = [];
index = 0;
getIndex()
return this.index;
back()
if (this.index > 0)
let command = this.commands[--this.index];
command.undo();
return this;
forward()
if (this.index < this.commands.length)
let command = this.commands[this.index++];
command.execute();
return this;
add(command)
if (this.commands.length)
this.commands.splice(this.index, this.commands.length - this.index);
this.commands.push(command);
this.index++;
return this;
clear()
this.commands.length = 0;
this.index = 0;
return this;
// use when you init your Canvas, like this.history = new CommandHistory();
-
那么您必须为您的命令实现命令类。
用于添加对象
class AddCommand
constructor(receiver, controller)
this.receiver = receiver;
this.controller = controller;
execute()
this.controller.addObject(this.receiver);
undo()
this.controller.removeObject(this.receiver);
// When you will add object on your canvas invoke also this.history.add(new AddCommand(object, controller))
用于移除对象
class RemoveCommand
constructor(receiver, controller)
this.receiver = receiver;
this.controller = controller;
execute()
this.controller.removeObject(this.receiver);
undo()
this.controller.addObject(this.receiver);
fabric.js 对每个对象 http://fabricjs.com/docs/fabric.Object.html#saveState 都有 saveState 方法。您可以使用它来实现转换命令,当您在画布上修改织物对象时,该命令将添加到历史对象中。
class TransformCommand
constructor(receiver, options = )
this.receiver = receiver;
this._initStateProperties(options);
this.state = ;
this.prevState = ;
this._saveState();
this._savePrevState();
execute()
this._restoreState();
this.receiver.setCoords();
undo()
this._restorePrevState();
this.receiver.setCoords();
// private
_initStateProperties(options)
this.stateProperties = this.receiver.stateProperties;
if (options.stateProperties && options.stateProperties.length)
this.stateProperties.push(...options.stateProperties);
_restoreState()
this._restore(this.state);
_restorePrevState()
this._restore(this.prevState);
_restore(state)
this.stateProperties.forEach((prop) =>
this.receiver.set(prop, state[prop]);
);
_saveState()
this.stateProperties.forEach((prop) =>
this.state[prop] = this.receiver.get(prop);
);
_savePrevState()
if (this.receiver._stateProperties)
this.stateProperties.forEach((prop) =>
this.prevState[prop] = this.receiver._stateProperties[prop];
);
现在您可以将命令添加到历史记录并执行或撤消它们。
【讨论】:
感谢您回答@Raman,一旦我清除了一些当前任务,我将尝试此解决方案。您是否尝试过此解决方案并亲自查看其性能? 是的,我当然有,它是从现有项目中获取的,我可以分享它 这是一个很好的快速问题,为什么我们要存储prevState
而我们可以做到具体的 command.execute()
或 this.commands[--this.index].execute()
?
如果我没记错的话,这个解决方案还存储所有object properties
,而不仅仅是状态之间的diffs
?
是的,我们存储所有对象属性的值,当前和上一个,可能您是对的,您可以尝试按照您的建议进行操作以上是关于Fabric JS - 使用 JSON diff 进行 UNDO 和 REDO 优化的主要内容,如果未能解决你的问题,请参考以下文章
在 Fabric.js 中重置画布并使用 JSON 重新加载
在 python 服务器上从 Fabric.js JSON 构造图像