欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用fabric實現(xiàn)恢復和撤銷功能的實例詳解

 更新時間:2024年06月30日 09:39:10   作者:日明  
在圖形編輯器中,撤銷和恢復是一個非常常見的功能了,但是搜了下,網上好像也沒有太多相關的文章 可能是因為canvas相關的資料確實太少了吧,所以本文給大家介紹了如何基于 fabric 實現(xiàn)恢復、撤銷功能,需要的朋友可以參考下

介紹

在圖形編輯器中,撤銷和恢復是一個非常常見的功能了,但是搜了下,網上好像也沒有太多相關的文章 可能是因為canvas相關的資料確實太少了吧

其實實現(xiàn)撤銷和恢復并不難,因為fabric是支持把當前畫布中的內容導出為json的,并也支持導入json到畫布中去

當我們有了這兩個基本的能力,剩下的本質上就是如何監(jiān)聽畫布狀態(tài)的變更和操作狀態(tài)如何存取的問題了

我這里用了比較簡單和直接的辦法,定義了 undoStack 和 redoStack 兩個 stack 來進行記錄和存取

class CanvasStateManager {
  protected canvas: canvas
  protected editor: IEditor
  private undoStack: string[] = []
  private redoStack: string[] = []
 
  constructor(canvas: Owl.ICanvas, editor: Owl.IEditor) {
    this.canvas = canvas
    this.editor = editor
  }
}

export default CanvasStateManager

何時更新存儲的狀態(tài)?

需要監(jiān)聽的方法

回到上面的問題,我們需要怎么監(jiān)聽畫布狀態(tài)的變更? 這個問題實際上很簡單,我們可以通過監(jiān)聽 fabric 的回調事件來進行處理 正常情況我認為監(jiān)聽

'object:added'
'object:removed'
'object:modified'
'object:skewing'

四個事件已經足夠收集到畫布的變更了,甚至 object:skewing 其實都不太有必要 并且考慮到有些情況下可能需要取消監(jiān)聽,所以我這里定義了兩個方法 initHistoryListener 和 offHistoryListener

class CanvasStateManager {
  protected canvas: canvas
  protected editor: IEditor
  private undoStack: string[] = []
  private redoStack: string[] = []
 
  constructor(canvas: Owl.ICanvas, editor: Owl.IEditor) {
    this.canvas = canvas
    this.editor = editor
    this.initHistoryListener()
  }

   initHistoryListener = async () => {
      this.canvas.on({
        [ICanvasEvent.OBJECT_ADDED]: this.saveStateIfNotRestoring,
        [ICanvasEvent.OBJECT_MODIFIED]: this.saveStateIfNotRestoring,
        [ICanvasEvent.OBJECT_REMOVED]: this.saveStateIfNotRestoring
      })
    }

    offHistoryListener = () => {
      this.canvas.off(ICanvasEvent.OBJECT_ADDED, this.saveStateIfNotRestoring)
      this.canvas.off(ICanvasEvent.OBJECT_MODIFIED, this.saveStateIfNotRestoring)
      this.canvas.off(ICanvasEvent.OBJECT_REMOVED, this.saveStateIfNotRestoring)
    }
}

export default CanvasStateManager

如何保存畫布變更的狀態(tài)

將當前畫布轉換為 json

 // 獲取當前畫布的 JSON 描述
const canvasState = this.canvas.toDatalessJSON()
const currentStateString = JSON.stringify(canvasState)

我這里用的是 toDatalessJSON() 方法,而不是 toJSON(),主要是因為以下的

  • toDatalessJSON主要用于在需要減小序列化后數(shù)據(jù)大小的情況下,特別是在處理復雜的SVG圖形時。由于SVG圖形載入后通常是以ObjectPaths來保存的,因此大的SVG圖形會有很多的Path數(shù)據(jù),直接序列化會導致JSON數(shù)據(jù)過長。
  • toDatalessJSON方法可以將這些Path數(shù)據(jù)用路徑來代替,以減小序列化后的數(shù)據(jù)量。但需要注意的是,這需要手動設置sourcePath以便在下次使用時能夠找到對應的資源。

toDatalessJSON 和 toJSON 的主要區(qū)別

  • toJSON方法會完整地將畫布上的所有對象及其屬性序列化為JSON數(shù)據(jù),包括Path等詳細數(shù)據(jù)。
  • toDatalessJSON則會嘗試優(yōu)化這些數(shù)據(jù),通過用路徑代替詳細數(shù)據(jù)來減小數(shù)據(jù)量。

判斷當前狀態(tài)和撤銷堆棧中最后一個狀態(tài)是否相同 我們這里需要做一個邊界的處理,如果當前保存的狀態(tài)和最后一個撤銷狀態(tài)相同的情況下,則不需要對它進行保存,避免有些多余的保存影響到了撤銷和恢復的功能

  // 判斷當前狀態(tài)和撤銷堆棧中最后一個狀態(tài)是否相同
  if (this.undoStack.length > 0) {
    const lastUndoStateString = this.undoStack[this.undoStack.length - 1]
    if (currentStateString === lastUndoStateString) {
      // 如果當前狀態(tài)和最后一個撤銷狀態(tài)相同,則不保存
      console.log('Current canvas state is identical to the last saved state. Skipping save.')
      return
    }
  }

將畫布狀態(tài)保存到撤銷堆棧

// 將畫布狀態(tài)保存到撤銷堆棧
  this.undoStack.push(currentStateString)

限制撤銷堆棧的大小以節(jié)省內存

我們這里限制一下保存的狀態(tài),避免在堆棧中保存了太多的狀態(tài)占用了太多內存,我這里就暫且只保存30步,當超出的情況下則把前面的給頂出去

private readonly maxUndoStackSize: number = 30 // 最大撤銷堆棧大小
...
// 限制撤銷堆棧的大小以節(jié)省內存
if (this.undoStack.length > this.maxUndoStackSize) {
  this.undoStack.shift() // 移除最舊的狀態(tài)
}

如何自定義保存的狀態(tài)或時機

有很多時候我們其實并不想每一步操作都進行保存,例如我們在進行批量創(chuàng)建操作時,由于我們實際上的操作是一個個插入的,如果我們只是單純地把每一步狀態(tài)都記錄了,那么我們在撤銷的時候也只會一個個撤回去,跟我們原本的一次性創(chuàng)建N個元素的操作并不是逆向操作 這時候我們就需要去自定義一些保存的時機了,我這里暫且定義了兩種方式:

  • 忽略下一次畫布變更的保存
  • 自定義停止在當前流程中的狀態(tài)保存,以及自定義開始保存

忽略下一次畫布變更的保存

這個其實很簡單,我們直接定義一個狀態(tài)位來記錄一下即可

// 用于忽略下一次操作的保存
private ignoreNextSave: boolean = false

ignoreNextStateSave = () => {
this.ignoreNextSave = true
}

在保存的時候將狀態(tài)位進行重置

  private saveStateIfNotRestoring = () => {
    if (!this.ignoreNextSave && this.hasListener) {
      this.saveCustomState()
    }
    this.ignoreNextSave = false // 重置標志
  }

自定義停止在當前流程中的狀態(tài)保存,以及自定義開始保存

這里跟上面其實差不多,也是定義了一個狀態(tài)位來保存當前是否屬于允許保存的情況

  private hasListener: boolean = true

  changeHistoryListenerStatus = (hasListener: boolean) => {
    this.hasListener = hasListener
  }

不過這里的狀態(tài)位就是由用戶自己控制了

自定義撤銷功能

在這里我們需要去處理的是,在恢復的過程中我們其實會存在多次觸發(fā)fabric回調的情況,所以我們在恢復的情況下需要暫時停止監(jiān)聽,等到操作完成后再注冊監(jiān)聽的事件

  customUndo = () => {
    if (this.undoStack.length > 1) {
      // 取消事件監(jiān)聽器
      this.offHistoryListener()
      // 將當前狀態(tài)彈出并保存到恢復堆棧
      this.redoStack.push(this.undoStack.pop()!)

      // 獲取撤銷后的狀態(tài)
      const previousState = this.undoStack[this.undoStack.length - 1]

      this.canvas.clear()
      // 臨時禁用事件監(jiān)聽, 但是點擊一次存在多次監(jiān)聽更新的情況下不管用,所以可以考慮手動去掉事件監(jiān)聽器
      this.isRestoring = true

      this.canvas.loadFromJSON(previousState, () => {
        // 重新注冊事件監(jiān)聽器
        this.initHistoryListener()
        this.canvas.renderAll()
        this.isRestoring = false
      })
    }
  }

自定義恢復功能

這里也和上面一樣

  customRedo = () => {
    if (this.redoStack.length > 0) {
      // 取消事件監(jiān)聽器
      this.offHistoryListener()
      // 將最后的恢復狀態(tài)彈出并保存到撤銷堆棧
      this.undoStack.push(this.redoStack.pop()!)

      // 獲取恢復的狀態(tài)
      const nextState = JSON.parse(this.undoStack[this.undoStack.length - 1])

      // 臨時禁用事件監(jiān)聽
      this.isRestoring = true

      this.canvas.clear()

      this.canvas.loadFromJSON(nextState, () => {
        // 重新注冊事件監(jiān)聽器
        this.initHistoryListener()
        this.canvas.renderAll()
        this.isRestoring = false
      })
    }
  }

整體實現(xiàn)

class CanvasStateManager {
  protected canvas: Owl.ICanvas
  protected editor: IEditor
  private undoStack: string[] = []
  private redoStack: string[] = []
  private isRestoring: boolean = false
  // 用于忽略下一次操作的保存
  private ignoreNextSave: boolean = false
  private hasListener: boolean = true
  private readonly maxUndoStackSize: number = 30 // 最大撤銷堆棧大小

  static apis = [
    'clearCustomHistory',
    'saveCustomState',
    'customUndo',
    'customRedo',
    'ignoreNextStateSave',
    'initHistoryListener',
    'offHistoryListener',
    'changeHistoryListenerStatus'
  ]

  constructor(canvas: Owl.ICanvas, editor: Owl.IEditor) {
    this.canvas = canvas
    this.editor = editor
    // 初始狀態(tài)
    this.saveCustomState()

    this.initHistoryListener()
  }

  private saveStateIfNotRestoring = () => {
    if (!this.isRestoring && !this.ignoreNextSave && this.hasListener) {
      console.log('saveStateIfNotRestoring -> saveCustomState')
      this.saveCustomState()
    }
    this.ignoreNextSave = false // 重置標志
  }

  clearCustomHistory = () => {
    this.undoStack = []
    this.redoStack = []
    this.saveCustomState()
  }

  saveCustomState = () => {
    // 獲取當前畫布的 JSON 描述
    const canvasState = this.canvas.toDatalessJSON()
    const currentStateString = JSON.stringify(canvasState)

    // 判斷當前狀態(tài)和撤銷堆棧中最后一個狀態(tài)是否相同
    if (this.undoStack.length > 0) {
      const lastUndoStateString = this.undoStack[this.undoStack.length - 1]
      if (currentStateString === lastUndoStateString) {
        // 如果當前狀態(tài)和最后一個撤銷狀態(tài)相同,則不保存
        console.log('Current canvas state is identical to the last saved state. Skipping save.')
        return
      }
    }

    // 將畫布狀態(tài)保存到撤銷堆棧
    this.undoStack.push(currentStateString)

    // 輸出保存信息
    console.log('saveCustomState', this.undoStack, this.redoStack)

    // 限制撤銷堆棧的大小以節(jié)省內存
    if (this.undoStack.length > this.maxUndoStackSize) {
      this.undoStack.shift() // 移除最舊的狀態(tài)
    }
  }

  customUndo = () => {
    if (this.undoStack.length > 1) {
      // 取消事件監(jiān)聽器
      this.offHistoryListener()
      // 將當前狀態(tài)彈出并保存到恢復堆棧
      this.redoStack.push(this.undoStack.pop()!)

      // 獲取撤銷后的狀態(tài)
      const previousState = this.undoStack[this.undoStack.length - 1]

      this.canvas.clear()
      // 臨時禁用事件監(jiān)聽, 但是點擊一次存在多次監(jiān)聽更新的情況下不管用,所以可以考慮手動去掉事件監(jiān)聽器
      this.isRestoring = true

      this.canvas.loadFromJSON(previousState, () => {
        // 重新注冊事件監(jiān)聽器
        this.initHistoryListener()
        this.canvas.renderAll()
        this.isRestoring = false
      })
    }
  }

  customRedo = () => {
    if (this.redoStack.length > 0) {
      // 取消事件監(jiān)聽器
      this.offHistoryListener()
      // 將最后的恢復狀態(tài)彈出并保存到撤銷堆棧
      this.undoStack.push(this.redoStack.pop()!)

      // 獲取恢復的狀態(tài)
      const nextState = JSON.parse(this.undoStack[this.undoStack.length - 1])

      // 臨時禁用事件監(jiān)聽
      this.isRestoring = true

      this.canvas.clear()

      this.canvas.loadFromJSON(nextState, () => {
        // 重新注冊事件監(jiān)聽器
        this.initHistoryListener()
        this.canvas.renderAll()
        this.isRestoring = false
      })
    }
  }

  ignoreNextStateSave = () => {
    this.ignoreNextSave = true
  }

  changeHistoryListenerStatus = (hasListener: boolean) => {
    this.hasListener = hasListener
  }

  initHistoryListener = async () => {
    this.canvas.on({
      [ICanvasEvent.OBJECT_ADDED]: this.saveStateIfNotRestoring,
      [ICanvasEvent.OBJECT_MODIFIED]: this.saveStateIfNotRestoring,
      [ICanvasEvent.OBJECT_REMOVED]: this.saveStateIfNotRestoring
    })
  }

  offHistoryListener = () => {
    this.canvas.off(ICanvasEvent.OBJECT_ADDED, this.saveStateIfNotRestoring)
    this.canvas.off(ICanvasEvent.OBJECT_MODIFIED, this.saveStateIfNotRestoring)
    this.canvas.off(ICanvasEvent.OBJECT_REMOVED, this.saveStateIfNotRestoring)
  }
}

export default CanvasStateManager

以上就是使用fabric實現(xiàn)恢復和撤銷功能的實例詳解的詳細內容,更多關于fabric實現(xiàn)恢復和撤銷的資料請關注腳本之家其它相關文章!

相關文章

  • JS實現(xiàn)留言板功能[樓層效果展示]

    JS實現(xiàn)留言板功能[樓層效果展示]

    小編最近在基于js實現(xiàn)留言板功能,實現(xiàn)的功能有發(fā)布人和發(fā)布內容做非空校驗,樓層效果展示和發(fā)布時間展示。具體實例代碼大家參考下本文
    2017-12-12
  • JS 自執(zhí)行函數(shù)原理及用法

    JS 自執(zhí)行函數(shù)原理及用法

    這篇文章主要介紹了JS 自執(zhí)行函數(shù)原理及技巧,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08
  • javascript 動態(tài)生成css代碼的兩種方法

    javascript 動態(tài)生成css代碼的兩種方法

    這篇文章主要介紹了javascript 動態(tài)生成css代碼的兩種方法,有時候我們需要利用js來動態(tài)生成頁面上style標簽中的css代碼,下面就給大家介紹兩種方法,需要的朋友可以參考下
    2017-03-03
  • Javascript中For In語句用法實例

    Javascript中For In語句用法實例

    這篇文章主要介紹了Javascript中For In語句用法,實例分析了javascript使用For In語句遍歷數(shù)組的技巧,需要的朋友可以參考下
    2015-05-05
  • javascript實現(xiàn)滾動效果的數(shù)字時鐘實例

    javascript實現(xiàn)滾動效果的數(shù)字時鐘實例

    這篇文章主要是介紹使用javascript來實現(xiàn)數(shù)字時鐘滾動的效果,非常實用,有需要的朋友們可以來參考學習。
    2016-07-07
  • layui 表格操作列按鈕動態(tài)顯示的實現(xiàn)方法

    layui 表格操作列按鈕動態(tài)顯示的實現(xiàn)方法

    今天小編就為大家分享一篇layui 表格操作列按鈕動態(tài)顯示的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • layui之table checkbox初始化時選中對應選項的方法

    layui之table checkbox初始化時選中對應選項的方法

    今天小編就為大家分享一篇layui之table checkbox初始化時選中對應選項的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • javascript表格隨機排序代碼

    javascript表格隨機排序代碼

    非常不錯的思路,用js實現(xiàn)表格的隨機排序,建議大家看代碼,學習編程思路
    2008-09-09
  • 前端實現(xiàn)文本超出指定行數(shù)顯示"展開"和"收起"效果詳細步驟

    前端實現(xiàn)文本超出指定行數(shù)顯示"展開"和"收起"效果詳細步驟

    本文介紹如何使用JavaScript原生代碼實現(xiàn)文本折疊展開效果,并提供方法指導如何在Vue或React等框架中修改實現(xiàn),詳細介紹了創(chuàng)建整體框架、設置樣式及利用JS控制元素的步驟,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • TypeScript實現(xiàn)單鏈表的示例代碼

    TypeScript實現(xiàn)單鏈表的示例代碼

    鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結構,本文主要介紹了TypeScript實現(xiàn)單鏈表的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-08-08

最新評論