基于 Fabric.js 实现元素撤销与恢复

1,023 阅读1分钟

前言

关于画板基本功能的实现,可以看这篇:基于 Fabric.js 实现一个简易画板

准备工作

完善一下 Sketchpad 类的实现:

  • isRecoverable 字段:表示当前是否可以进行恢复操作,以区分已经撤销了部分元素但又重新添加或粘贴了新元素的情况,这种情况是无法继续恢复的。

  • maxLimit 字段:表示撤销操作的最大限制数量。

  • stateStack 字段:存储被撤销的元素列表。

// sketchpad.ts
interface Instance extends Object {
  [key: string]: any; // 兼容自定义属性
}

interface FabricObject extends Instance {
  top: number;
  left: number;
}

class Sketchpad {
  // ...
  public instance: Canvas;
  
  private isRecoverable = false;

  private stateStack: Array<Instance> = [];
  
  private maxLimit = 5;

  getObjects() {
    return this.instance.getObjects() as FabricObject[]; // 获取画板上的所有元素
  }
}

具体代码实现

// sketchpad.ts
insertShape(type: ShapeType) {
  const shape: Instance = createShape(type); // 生成对应类型的实例
  shape.top = 150;
  shape.left = 150;
  this.instance.add(shape).renderAll(); // 添加元素到画板上,并重新渲染画板
  
  this.isRecoverable = false; // 添加元素后,无法进行恢复操作
}

undo() {
  const target = this.getAllObjects().pop() as FabricObject; // 获取最新添加的元素

  // 如果撤销列表已满,移除最早的记录,再加入最新记录
  if (this.stateStack.length >= this.maxLimit) {
    this.stateStack.shift();
  }
  this.stateStack.push(target);

  this.instance.remove(target).renderAll(); // 从画板上移除元素,并重新渲染画板
  this.isRecoverable = true; // 撤销元素后,允许进行恢复操作
}

redo() {
  // 当前允许恢复元素,并且撤销列表中存在元素
  if (this.isRecoverable && this.stateStack.length > 0) {
    const target = this.stateStack.pop() as FabricObject;
    this.instance.add(target).renderAll(); // 重新添加元素到画板上
  } else {
    this.stateStack = []; // 清空当前撤销列表,不可恢复
  }
}

函数实现已完成,接下来分别展示未限制和限制了最大撤销空间的效果:

不限制最大撤销空间的效果.gif

限制了最大撤销空间的效果.gif

最后

目前的实现方式比较简单,只能反应出元素的增减,而不能反应出元素的变化。

欢迎大家提出建议,一起进步。