最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

介紹

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

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

當我們有了這兩個基本的能力,剩下的本質(zhì)上就是如何監(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 的回調(diào)事件來進行處理 正常情況我認為監(jiān)聽

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

四個事件已經(jīng)足夠收集到畫布的變更了,甚至 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)

將當前畫布轉(zhuǎn)換為 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é)省內(nèi)存

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

private readonly maxUndoStackSize: number = 30 // 最大撤銷堆棧大小
...
// 限制撤銷堆棧的大小以節(jié)省內(nèi)存
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
}

在保存的時候?qū)顟B(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回調(diào)的情況,所以我們在恢復的情況下需要暫時停止監(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é)省內(nèi)存
    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)恢復和撤銷功能的實例詳解的詳細內(nèi)容,更多關于fabric實現(xiàn)恢復和撤銷的資料請關注腳本之家其它相關文章!

相關文章

最新評論

大英县| 闸北区| 通化县| 新河县| 邢台市| 铅山县| 黑山县| 福鼎市| 东乡| 丰都县| 曲靖市| 阿荣旗| 扬中市| 延安市| 丰县| 宁德市| 汉川市| 株洲市| 崇左市| 青铜峡市| 惠州市| 平南县| 德化县| 和硕县| 海晏县| 宽城| 富宁县| 东乡族自治县| 巴南区| 广宁县| 云梦县| 霍州市| 米脂县| 湄潭县| 浠水县| 布尔津县| 博乐市| 乐平市| 乌海市| 万盛区| 鄯善县|