亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

介紹

在圖形編輯器中,撤銷和恢復(fù)是一個非常常見的功能了,但是搜了下,網(wǎng)上好像也沒有太多相關(guān)的文章 可能是因?yàn)閏anvas相關(guān)的資料確實(shí)太少了吧

其實(shí)實(shí)現(xiàn)撤銷和恢復(fù)并不難,因?yàn)閒abric是支持把當(dāng)前畫布中的內(nèi)容導(dǎo)出為json的,并也支持導(dǎo)入json到畫布中去

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

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

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)的變更? 這個問題實(shí)際上很簡單,我們可以通過監(jiān)聽 fabric 的回調(diào)事件來進(jìn)行處理 正常情況我認(rèn)為監(jiān)聽

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

四個事件已經(jīng)足夠收集到畫布的變更了,甚至 object:skewing 其實(shí)都不太有必要 并且考慮到有些情況下可能需要取消監(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)

將當(dāng)前畫布轉(zhuǎn)換為 json

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

我這里用的是 toDatalessJSON() 方法,而不是 toJSON(),主要是因?yàn)橐韵碌?/p>

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

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

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

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

  // 判斷當(dāng)前狀態(tài)和撤銷堆棧中最后一個狀態(tài)是否相同
  if (this.undoStack.length > 0) {
    const lastUndoStateString = this.undoStack[this.undoStack.length - 1]
    if (currentStateString === lastUndoStateString) {
      // 如果當(dāng)前狀態(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步,當(dāng)超出的情況下則把前面的給頂出去

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

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

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

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

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

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

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

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

在保存的時候?qū)顟B(tài)位進(jìn)行重置

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

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

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

  private hasListener: boolean = true

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

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

自定義撤銷功能

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

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

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

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

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

自定義恢復(fù)功能

這里也和上面一樣

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

      // 獲取恢復(fù)的狀態(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
      })
    }
  }

整體實(shí)現(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 // 重置標(biāo)志
  }

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

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

    // 判斷當(dāng)前狀態(tài)和撤銷堆棧中最后一個狀態(tài)是否相同
    if (this.undoStack.length > 0) {
      const lastUndoStateString = this.undoStack[this.undoStack.length - 1]
      if (currentStateString === lastUndoStateString) {
        // 如果當(dāng)前狀態(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()
      // 將當(dāng)前狀態(tài)彈出并保存到恢復(fù)堆棧
      this.redoStack.push(this.undoStack.pop()!)

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

      this.canvas.clear()
      // 臨時禁用事件監(jiān)聽, 但是點(diǎ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()
      // 將最后的恢復(fù)狀態(tài)彈出并保存到撤銷堆棧
      this.undoStack.push(this.redoStack.pop()!)

      // 獲取恢復(fù)的狀態(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實(shí)現(xiàn)恢復(fù)和撤銷功能的實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于fabric實(shí)現(xiàn)恢復(fù)和撤銷的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

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

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

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

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

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

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

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

    Javascript中For In語句用法實(shí)例

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

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

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

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

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

    layui之table checkbox初始化時選中對應(yīng)選項(xiàng)的方法

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

    javascript表格隨機(jī)排序代碼

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

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

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

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

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

最新評論