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 重新加载

fabric.js - 克隆的路径组不从 JSON 呈现

在 python 服务器上从 Fabric.js JSON 构造图像

从 JSON 加载不起作用。 Fabric.JS

Fabric.js 合并多个 Canvas JSON(或)SVG

Fabric.js 对象在 DOM 加载时从 JSON 呈现时无法更改填充颜色