web-dev-qa-db-ja.com

Fabric.jsのやり直しを元に戻す

Fabric.jsキャンバスに元に戻す/やり直し機能を追加しようとしています。私の考えは、キャンバスの変更をカウントするカウンターを用意することです(現在はオブジェクトの追加をカウントします)。私は自分の配列にJSONとして全体のキャンバスをプッシュ状態配列を、持っています。

それから私は単純にして状態をリコールしたいです

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

ユーザーが[元に戻す]をクリックすると、ctrが1つ減り、配列から状態が読み込まれます。ユーザーがREDOをクリックすると、ctrが1つ増加し、配列から状態をロードします。

私は単純な数でこれを体験すると、すべてが正常に動作します。実際のファブリックキャンバスでは、いくつかの問題が発生します->実際には機能しません。これは私のイベントハンドラーに依存していると思います

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

jsfiddle ここにあります:

これが実際の数値のみの例です(結果はコンソールを参照してください): jsFiddle

16
gco

私は自分でこれに答えました。

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.

図面とオブジェクトの両方を処理するには、まだ改善が必要です。しかし、それは簡単なはずです。

20
gco

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サークルキューを使用して履歴の長さを制限することをお勧めします。

4

重要なことの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の元に戻す-やり直し機能

3
Kirby

キャンバス上に多くのオブジェクトがある場合、キャンバス全体をJSONにシリアル化するとコストがかかる可能性があります。全体として、2つのアプローチがあります。

  • 状態全体(選択した状態)を保存します
  • アクションを保存する

詳細については ここ を読むことができます。

元に戻す/やり直しを実装する別のアプローチは、より効率的なコマンドパターンです。実装については、 ここ を見て、他の人の経験については(状態とアクション) ここ

ここには、実装の戦略に関する優れた洞察もあります。

3
bolshchikov

Bolshchikovが言及しているように、州全体を保存することは費用がかかります。 「機能」しますが、うまく機能しません。

あなたの状態の履歴は小さな変更で膨らみます、そしてそれはあなたが元に戻す/やり直すたびにキャンバス全体を最初から再描画しなければならないというパフォーマンスの打撃については何も言いません...

過去に使用したものと現在使用しているものは、コマンドパターンです。私はこの(一般的な)ライブラリがうなり声の作業に役立つことを発見しました: https://github.com/strategydynamics/commandant

実装を開始したばかりですが、これまでのところかなりうまく機能しています。

一般的なコマンドパターンを要約すると、次のようになります。

  1. あなたは何かをしたいです。例:キャンバスにレイヤーを追加する
  2. レイヤーを追加するメソッドを作成します。例:do {canvas.add(...)}
  3. レイヤーを削除するメソッドを作成します。例:元に戻す{canvas.remove(...)}

次に、レイヤーを追加する場合。レイヤーを直接追加する代わりに、コマンドを呼び出します。

非常に軽量でうまく機能します。

1
Cory Mawhorter