簡體   English   中英

撤消Fabric.js的重做

[英]Undo Redo for Fabric.js

我正在嘗試為我的Fabric.js畫布添加撤消/重做功能。 我的想法是有一個計數器來計算畫布修改(現在它計算對象的添加)。 我有一個狀態數組,它將整個畫布作為JSON推送到我的數組。

然后我只想回憶起各州

canvas.loadFromJSON(state[state.length - 1 + ctr],

當用戶點擊undo時,ctr將減1並將狀態加載到數組之外; 當用戶點擊重做時,ctr將增加1並將狀態加載到數組之外。

當我用簡單的數字體驗這一點時,一切正常。 使用真正的面料帆布,我遇到了一些麻煩 - >它不起作用。 我認為這取決於我的事件處理程序

canvas.on({
   'object:added': countmods
});

jsfiddle在這里:

這里是工作數字唯一的例子(結果見控制台): jsFiddle

我自己回答了這個問題。

jsfiddle

我做了什么:

if (savehistory === true) {
    myjson = JSON.stringify(canvas);
    state.push(myjson);
} // this will save the history of all modifications into the state array, if enabled

if (mods < state.length) {
    canvas.clear().renderAll();
    canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
    canvas.renderAll();
    mods += 1;
} // this will execute the undo and increase a modifications variable so we know where we are currently. Vice versa works the redo function.

仍然需要改進來處理圖紙和物體。 但這應該很簡單。

您可以使用diff-patchtracking object version 首先,您監聽所有對象更改:object:created,object:modified ....,通過在變量中保存canvas.toObject()來保存畫布的第一個快照; 下次運行diffpatcher .diff(snapshot,canvas.toObject()),並僅保存補丁。 要撤消,可以使用diffpatcher.reverse這些補丁。 要重做,只需使用函數diffpatcher.patch。 通過這種方式,您可以節省內存,但會占用更多的CPU。

使用fabricjs,您可以使用Object#saveState()和處理對象:添加以將原始狀態保存到數組(用於撤消任務),偵聽對象:已修改,對象:刪除(用於重做任務)。 這種方式更輕巧,更容易實現。 更多通過使用圈隊列來限制歷史長度更好。

如果畫布上有許多對象,將整個畫布序列化為JSON可能會很昂貴。 總而言之,有兩種方法:

  • 拯救整個州(你選擇的那個)
  • 保存動作

可以在這里閱讀更多。

實現撤消/重做的另一種方法是可能更有效的命令模式。 為了實現,看這里 ,和其他人的經驗(與狀態動作) 在這里

這里還有很好的實施策略見解。

一個重要的事情是最終的canvas.renderAll()應該在傳遞給loadFromJSON()的第二個參數的回調中調用,就像這樣

canvas.loadFromJSON(state, function() {
    canvas.renderAll();
}

這是因為解析和加載JSON可能需要幾毫秒,您需要等到渲染之前完成。 一旦點擊它們就禁用撤消和重做按鈕並且僅在同一回叫中重新啟用它也很重要。 像這樣的東西

$('#undo').prop('disabled', true);
$('#redo').prop('disabled', true);    
canvas.loadFromJSON(state, function() {
    canvas.renderAll();
    // now turn buttons back on appropriately
    ...
    (see full code below)
}

我有一個撤銷和重做堆棧以及最后一個未更改狀態的全局。 當發生一些修改時,先前的狀態被推入撤銷堆棧並重新捕獲當前狀態。

當用戶想要撤消時,則將當前狀態推送到重做堆棧。 然后我彈出最后一次撤消,並將其設置為當前狀態並在畫布上渲染它。

同樣,當用戶想要重做時,當前狀態被推送到撤消堆棧。 然后我彈出最后一個重做並將它們設置為當前狀態並在畫布上渲染它。

代碼

  // Fabric.js Canvas object var canvas; // current unsaved state var state; // past states var undo = []; // reverted states var redo = []; /** * Push the current state into the undo stack and then capture the current state */ function save() { // clear the redo stack redo = []; $('#redo').prop('disabled', true); // initial call won't have a state if (state) { undo.push(state); $('#undo').prop('disabled', false); } state = JSON.stringify(canvas); } /** * Save the current state in the redo stack, reset to a state in the undo stack, and enable the buttons accordingly. * Or, do the opposite (redo vs. undo) * @param playStack which stack to get the last state from and to then render the canvas as * @param saveStack which stack to push current state into * @param buttonsOn jQuery selector. Enable these buttons. * @param buttonsOff jQuery selector. Disable these buttons. */ function replay(playStack, saveStack, buttonsOn, buttonsOff) { saveStack.push(state); state = playStack.pop(); var on = $(buttonsOn); var off = $(buttonsOff); // turn both buttons off for the moment to prevent rapid clicking on.prop('disabled', true); off.prop('disabled', true); canvas.clear(); canvas.loadFromJSON(state, function() { canvas.renderAll(); // now turn the buttons back on if applicable on.prop('disabled', false); if (playStack.length) { off.prop('disabled', false); } }); } $(function() { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Set up the canvas canvas = new fabric.Canvas('canvas'); canvas.setWidth(500); canvas.setHeight(500); // save initial state save(); // register event listener for user's actions canvas.on('object:modified', function() { save(); }); // draw button $('#draw').click(function() { var imgObj = new fabric.Circle({ fill: '#' + Math.floor(Math.random() * 16777215).toString(16), radius: Math.random() * 250, left: Math.random() * 250, top: Math.random() * 250 }); canvas.add(imgObj); canvas.renderAll(); save(); }); // undo and redo buttons $('#undo').click(function() { replay(undo, redo, '#redo', this); }); $('#redo').click(function() { replay(redo, undo, '#undo', this); }) }); 
 <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" type="text/javascript"></script> </head> <body> <button id="draw">circle</button> <button id="undo" disabled>undo</button> <button id="redo" disabled>redo</button> <canvas id="canvas" style="border: solid 1px black;"></canvas> </body> 

請注意,Fabric.js中存在類似的問題, Undo-Redo功能

正如bolshchikov所提到的,拯救整個州是昂貴的。 它會“起作用”,但效果不佳。

你的狀態歷史會隨着小的變化而膨脹,而且每次你​​撤消/重做時都不必重新繪制整個畫布,因此沒有任何關於性能的信息......

我過去使用的以及我現在使用的是命令模式。 我找到了這個(通用)庫來幫助完成繁瑣的工作: https//github.com/strategydynamics/commandant

剛剛開始實施它,但它到目前為止工作得很好。

總結一下命令模式:

  1. 你想做點什么。 例如:在畫布上添加一個圖層
  2. 創建添加圖層的方法。 例如:{canvas.add(...)}
  3. 創建一個刪除圖層的方法。 例如:撤消{canvas.remove(...)}

然后,當您想要添加圖層時。 您可以調用命令而不是直接添加圖層。

非常輕巧,效果很好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM