iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
Modern Web

一起來玩圖像編輯器:Fabric.js 的實戰修煉系列 第 19

Day19-修改畫布 prototype 的入門-拆解 fabric-history prototype (1)

  • 分享至 

  • xImage
  •  

今天不開發新功能,今天來拆解別人的扣~

來說說 prototype 要怎麼改寫!在那之前,先來認識我們的例子 fabric-history 是怎麼寫的。

alimozdemir/fabric-history: Fabric.js history plugin (github.com)

官方文件給的只有這兩個 api

canvas.undo();
canvas.redo();

但其實裡面定義好的自定義 prototype api 的都可以拿出來用,甚至可以修改裡面邏輯 or 新增 api
fabric-history/src/index.js at master · alimozdemir/fabric-history (github.com)

每個 api 的作用簡介:

  1. 覆寫 initialize 和 dispose 函數:

    • initialize 函數中調用 _historyInit() 來初始化歷史記錄相關的變數和事件監聽器。
    • dispose 函數中調用 _historyDispose() 來移除事件監聽器。
  2. 管理歷史記錄:

    • _historyNext() 方法獲取當前畫布狀態的 JSON 表示。
    • _historyEvents() 方法定義了需要監聽的事件,包括 'object:added''object:removed''object:modified''object:skewing'
    • _historySaveAction() 方法在發生這些事件時,將當前畫布狀態推入 historyUndo 堆疊。
  3. 復原和重做操作:

    • undo() 方法從 historyUndo 堆疊中彈出最新的狀態,並將其推入 historyRedo 堆疊,然後重新渲染畫布。
    • redo() 方法從 historyRedo 堆疊中彈出最新的狀態,並將其推入 historyUndo 堆疊,然後重新渲染畫布。
  4. 其他輔助方法:

    • clearHistory() 方法清空 historyUndohistoryRedo 堆疊。
    • onHistory()offHistory() 方法用於控制是否記錄歷史操作。
    • canUndo()canRedo() 方法用於檢查是否有可撤銷或重做的操作。
  • initHistory() 方法用於初始化撤銷/重做堆疊,並註冊 'object:modified' 事件來更新歷史記錄。
  • undo()redo() 方法分別實現了撤銷和重做操作。

我們先不要看文件裡那些比較複雜的東西,我們先試著用最簡單的三個 function 來達成動作紀錄就好:

用一般 js 的方式寫

如果這功能的簡易版用一般 js 的方式寫:

// 紀錄復原或重做的 array
const undoStack = [];
const redoStack = [];

// 註冊自定義事件(記錄歷史)
const saveHistory = (event) => {
  undoStack.push(canvas.toJSON());
  redoStack.length = 0; // 清空重做堆疊
}

// 我們讓記錄歷史這件事,在新增、刪除、修改物件時觸發
canvas.on('object:added', saveHistory);
canvas.on('object:removed', saveHistory);
canvas.on('object:modified', saveHistory);

// 按下復原時
const undo = () => {
  if (undoStack.length > 0) {
    redoStack.push(canvas.toJSON());
    canvas.loadFromJSON(undoStack.pop(), () => {
      canvas.renderAll();
    });
  }
};

// 按下重做時
const redo = () => {
  if (redoStack.length > 0) {
    undoStack.push(canvas.toJSON());
    canvas.loadFromJSON(redoStack.pop(), () => {
      canvas.renderAll();
    });
  }
};

改寫進 prototype

但歷史紀錄其實是一個反覆且執行頻率高的行為,我們可以考慮將之改寫進 prototype。

使用者可以輕鬆地撤銷和重做在畫布上進行的各種操作,並可以自定義需要記錄的事件。這對於需要頻繁修改和編輯的應用程序非常有用,且會讓呼叫此 function 執行更加容易(在有使用 fabricjs 的全域都可以呼叫)

這個轉換其實寫起來不會跟上面function的寫法差很多,只是架構換了一下,一起來看看吧

 fabric.Canvas.prototype.initHistory = function() {
      this.undoStack = [];
      this.redoStack = [];

	// 加上監聽器
      this.on('object:added', (e) => this._saveHistory(e));
      this.on('object:removed', (e) => this._saveHistory(e));
      this.on('object:modified', (e) => this._saveHistory(e));
    };

比較大的不同是,我們 把 undoStack 和 redoStack 直接定義在 canvas 物件的原型上,而不是作為外部變數。這樣可以確保每個 canvas 實例都有自己的 undoStack 和 redoStack

而且在開始其他動作之前我們會初始化這個canvas的歷史狀態。

// 註冊自定義事件(記錄歷史)
  fabric.Canvas.prototype._saveHistory = function() {
      this.undoStack.push(this.toJSON());
	  this.redoStack.length = 0; // 清空重做堆疊
  }
 

// 按下復原時
    fabric.Canvas.prototype.undo = function() {
      if (this.undoStack.length > 0) {
        this.redoStack.push(this.toJSON());
        this.loadFromJSON(this.undoStack.pop(), () => {
          this.renderAll();
        });
      }
    };

// 按下重做時
    fabric.Canvas.prototype.redo = function() {
      if (this.redoStack.length > 0) {
        this.undoStack.push(this.toJSON());
        this.loadFromJSON(this.redoStack.pop(), () => {
          this.renderAll();
        });
      }
    };

這邊其實沒有變化太大,很籠統地說,只是將 canvas 全都換成了 this 了。

當你使用普通的function來實現功能時,這些function通常是獨立的,並且不會與特定的對象實例關聯。這種方法適合於簡單的功能或工具函數。

但當你使用 prototype 來實現功能時,這些方法會被添加到對象的原型上,並且可以被所有該對象的實例共享。這種方法適合於需要在多個實例之間共享行為的情況。像歷史紀錄這全面都會被共用的東西就很適合。


差異總結

  1. 作用範圍

    • Function:函數是獨立的,可以在任何地方調用,並且需要顯式地傳遞 canvas 對象。
    • Prototype:方法被添加到 fabric.Canvas 的原型上,所有 fabric.Canvas 的實例都可以直接調用這些方法。
  2. 代碼重用

    • Function:每次調用函數時都需要傳遞 canvas 對象,適合於簡單的工具函數。
    • Prototype:方法被共享在所有實例之間,適合於需要在多個實例之間共享行為的情況。
  3. 設計模式

    • Function:適合於功能性編程,函數是獨立的。
    • Prototype:適合於面向對象編程,方法與對象實例關聯。

選擇哪種方法取決於你的應用場景和設計需求。如果你需要在多個 fabric.Canvas 實例之間共享行為,使用 prototype 會更合適。如果你只是需要一些簡單的工具函數,使用普通的 function 會更簡潔。


上一篇
Day18-fabric.js 進階組合技! 畫布縮放與平移:實現像 illustrator 一樣的縮放和拖動功能
下一篇
Day20- 修改畫布 prototype 的入門-拆解 fabric-history prototype (2)
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言