アンドゥとリドゥの実装
アンドゥはUndoのことで、直前の操作を取り消し元に戻す機能です。リドゥはRedoのことで、元に戻した操作を再びやり直す機能です。アンドゥを繰り返すと絵を描く操作を徐々に何も無い状態にまで逆再生するように戻すことができます。そこからリドゥを繰り返すと絵を描いていくプロセスが徐々に再現されます。
アンドゥとリドゥをマウスホイールを回すことでお絵描きのプロセスをアニメーションのように見ることができるのが、このお絵描きアプリの最大の特徴です。

マウスホイールでアンドゥやリドゥ
次のサンプルコード[1]を追記してプロジェクトをデバッグビルドして実行したら、マウスホイールでアンドゥやリドゥができるようになります。WindowsとmacOSとLinuxでそれぞれデフォルトでは、マウスホイールの回転方向が異なる可能性があります。
アンドゥとリドゥは一筆書きする現在の頂点データ数である_current変数を加算減算するだけで実装できます。一筆書きの線画は0~_current変数までベジェ曲線を繋いで描きます。_pos配列の要素数は頂点ごとに4要素あるので_currentの数値の4倍です。
こうすることでこのアプリに限り、今後、第6回と第7回で独自形式ファイルを開いたり保存したりしても全て最初までアンドゥできます。普通はファイルを開いた時の状態までしかアンドゥできません。

(前略) // 全てのDOMが読み込み完了したら window.addEventListener("DOMContentLoaded", () => { // マウスがクリックされたら頂点描画 _canvas.addEventListener("mousedown", (e) => { _click = true; _pos.length = _current*4; let pos = getPos(e); _pos.push(pos[0],pos[1],pos[0],pos[1]); _current++; line(); }); // マウスがドラッグされたら window.addEventListener("mousemove", (e) => { if ( _click ) { let pos = getPos(e); _pos[_pos.length-4] = pos[0]; _pos[_pos.length-3] = pos[1]; line(); } }); // マウスが離されたら window.addEventListener("mouseup", (e) => { _click = false; }); // マウスホイールでアンドゥとリドゥ _canvas.addEventListener('wheel', (e) => { if (e.deltaY > 0) { if (_current > 0) _current--; } else if (e.deltaY < 0) { if (_current < _pos.length/4) _current++; } line(); }) window.addEventListener("resize", resize, false); resize(); }); (後略)
サンプルコードの解説
マウスが押されたら、_pos配列のサイズlengthプロパティを頂点データ数_current変数の4倍(頂点データごとに4要素ずつあるから)にします。_current変数は1ずつ加算するように変更します。それから「line」関数を使って一筆書きします。
マウスホイールが回されたら、回転がプラスなら現在の頂点データ数_current変数を1減算します。回転がマイナスなら_current変数を1加算します。それからline関数を呼んで一筆書きします。
コラム:お絵描きアプリのアンドゥとリドゥの表示ルーチン
ペイントツールのアンドゥとリドゥは、毎回全てのピクセルの色を描画していると思います。それに対しこのお絵描きアプリは、全画面を白色で塗り潰したのち、毎回頂点とベジェ曲線だけで全てを描いています。それによりペイントツールのように全てのピクセルの色を描画するより高速に処理できます。
もしアンドゥデータとリドゥデータをペイントツールのように全てのピクセルのデータを保持したり取得したりすると、マウスホイールでアンドゥやリドゥの処理が追いつかず、おかしなことになりかねません。