Javascript プログラミング

fabric.jsのお絵かきアプリにUNDO/REDOを追加

やっぱりUNDO / REDOは欲しい

まだfabric.jsで遊んでいます。

やっぱりイマドキのアプリでUndo/Redoがないと使いにくいですよね。

Undo/Redoの存在はデジタル手書きの大きなメリットです。

ちょっと調べてみた感じでは、fabric.js自身にはUndo/Redoの機能は無いようです。

いろんな人がそれぞれの方法でUndo/Redoを実装していましたが、自分に合ったUndo/Redoは自分で作るしたないみたいです。

最初はcanvas.getObjects()で全オブジェクトを配列として取得して、それを後ろから消していく方法を考えました。

しかし、これだとオブジェクトの移動や回転、拡大とかの編集に対応できない問題があります。

その代わりに履歴の途中に挿入するようなことも可能になります。

今回は履歴の途中への挿入は要らないがオブジェクト編集の取り消しは欲しいと思いました。

 

canvasの内容をJSONで配列に記録しておく

最終的に作ったUNDOは正攻法です。

UNDO用の配列を用意しておき、記録したい描画や編集が行われる度にcanvasの内容をJSONとして配列にPUSHしていきます。

 javascript
const undo_history=[];
undo_history.push(JSON.Stringify(canvas));

 

UNDOするときには、配列の最後から一つ前の要素をcanvasに渡して再描画させます。

 javascript
const content = undo_history[undo_history.length - 1];
canvas.loadFromJSON(content, function () {
canvas.renderAll();
});

canvas.on〜にUNDO配列への記録処理を設定

描画や編集が行われるたびにUNDO配列に記録するためには、canvas.on〜のイベントに処理を設定します。

今回使ったのは canvas.on('object:added',と canvas.on('object:modified' の二つのイベントです。

オブジェクトが追加されたときと、編集されたときに発火します。

 javascript
canvas.on('object:added', function () {
undo_history.push(JSON.stringify(canvas));
});
 
canvas.on('object:modified', function () {
undo_history.push(JSON.stringify(canvas));
});

また、Redo用にも別途配列を用意して、UNDOで取り消したcanvasの状態を記録しています。

 

Undo/Redoで書き戻すときのイベントを無視させる

前述のように、UNDO処理では canvas.loadFromJSON() に以前のcanvasの内容を渡して描画させます。

このとき、JSONに記録されているオブジェクトを描画する度にobject:addedイベントが発火します。

object:addedイベントにはUNDO記録処理が設定してあるので、UNDOの描画過程がUNDO配列にどんどん書き込まれていくという、ややこしいことになってしまいます。

そこで"lockHistory"というフラグを用意して、UNDOデータから書き戻している間はlockHistoryをtrueにするようにしました。

object:addedイベントに設定する処理では、lockHistoryがtrueの時は無視するようにしました。

 javascript
canvas.on('object:added', function () {
if (lockHistory) return;
undo_history.push(JSON.stringify(canvas));
});

 

削除処理への対応はイベントに頼らずに削除関数内で処理

オブジェクトを削除したときも、 canvas.on('object:removed', というイベントを使って扱えます。

ただ、編集モードで複数のオブジェクトを選択して一気に削除したとき、オブジェクトの数だけobject:removedイベントが発火してしまいます。

まとめて消したときは履歴も一回にしたいところです。

これは私が作った削除機能の実装が、canvas.getActiveObjects()で取得したオブジェクトの配列をひとつずつ消しているためです。

選択されたオブジェクトをひとつのActiveObjectとして扱うことができないかと、あれこれ試してみたのですが、結局うまくいきませんでした。

そこで、イベントの発火に頼らずに削除用の関数内で、オブジェクトを削除したあとでcanvasをUNDO配列に保存するようにしました。

 

UNDO/REDO付きお絵かきアプリのサンプル

そのほか、細かいところを調整してできあがったのが以下のようなコードです。

配列のpushとかpopとかやると頭を使うのでプログラムやってんなーっていう実感がしますね。

 

See the Pen
fabric.js free drawing undo/redo with edit, delete object
by Ryoji Kimura (@ryjkmr)
on CodePen.

 

 

-Javascript, プログラミング
-