Fabric.jsキャンバスに元に戻す/やり直し機能を追加しようとしています。私の考えは、キャンバスの変更をカウントするカウンターを用意することです(現在はオブジェクトの追加をカウントします)。私は自分の配列にJSONとして全体のキャンバスをプッシュ状態配列を、持っています。
それから私は単純にして状態をリコールしたいです
canvas.loadFromJSON(state[state.length - 1 + ctr],
ユーザーが[元に戻す]をクリックすると、ctrが1つ減り、配列から状態が読み込まれます。ユーザーがREDOをクリックすると、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-patch
またはtracking object version
のようなものを使用できます。まず、すべてのオブジェクトの変更をリッスンします。object:created、object:modified ....、canvas.toObject()を変数に保存して、キャンバスの最初のスナップショットを保存します。次回は、 diffpatcher 。diff(snapshot、canvas.toObject())を実行し、パッチのみを保存します。元に戻すには、diffpatcher.reverseこれらのパッチを使用できます。やり直すには、関数diffpatcher.patchを使用するだけです。このようにして、メモリを節約できますが、CPU使用率が高くなります。
Fabricjsを使用すると、Object#saveState()を使用してobject:addedを処理し、元の状態を配列に保存し(タスクを元に戻す場合)、object:modified、object:removing(タスクをやり直す場合)をリッスンできます。この方法は、より軽量で、実装が非常に簡単です。 moreサークルキューを使用して履歴の長さを制限することをお勧めします。
重要なことの1つは、最後のcanvas.renderAll()
は、次のようにloadFromJSON()
の2番目のパラメーターに渡されるコールバックで呼び出される必要があるということです。
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)
}
元に戻すスタックとやり直しスタック、および最後の変更されていない状態のグローバルがあります。何らかの変更が発生すると、前の状態が元に戻すスタックにプッシュされ、現在の状態が再キャプチャされます。
ユーザーが元に戻す必要がある場合、現在の状態がREDOスタックにプッシュされます。次に、最後の元に戻すをポップオフし、両方を現在の状態に設定して、キャンバスにレンダリングします。
同様に、ユーザーがやり直したい場合、現在の状態は元に戻すスタックにプッシュされます。次に、最後のやり直しをポップオフし、両方とも現在の状態に設定して、キャンバスにレンダリングします。
コード
// 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の元に戻す-やり直し機能
Bolshchikovが言及しているように、州全体を保存することは費用がかかります。 「機能」しますが、うまく機能しません。
あなたの状態の履歴は小さな変更で膨らみます、そしてそれはあなたが元に戻す/やり直すたびにキャンバス全体を最初から再描画しなければならないというパフォーマンスの打撃については何も言いません...
過去に使用したものと現在使用しているものは、コマンドパターンです。私はこの(一般的な)ライブラリがうなり声の作業に役立つことを発見しました: https://github.com/strategydynamics/commandant
実装を開始したばかりですが、これまでのところかなりうまく機能しています。
一般的なコマンドパターンを要約すると、次のようになります。
次に、レイヤーを追加する場合。レイヤーを直接追加する代わりに、コマンドを呼び出します。
非常に軽量でうまく機能します。