JavaScript
HTML5
jQuery
canvas

Canvasで元に戻す(Undo),やり直し(Redo)を実装する方法

これを作った時の知見を忘れないうちにメモ

getImageDataとputImageData

canvasのcontext.getImageData()を用いることで、描画されているラスタのデータを範囲指定してピクセルの配列(ImageData)として取得することができます。
また、取得したImageDataはputImageData()で描画を行うことができます。

ImageDataをスタックとして変数に保持しておけば元に戻す・やり直すという動作を実現させることができます。
それぞれの配列の挙動はFIFOになるように実装します。

サンプルコード

let $canvas = $('#myCanvas');
// contextを取得
let ctx = $canvas[0].getContext('2d');
// スタックしておく最大回数。キャンバスの大きさの都合などに合わせて調整したら良いです。
const STACK_MAX_SIZE = 5;
// スタックデータ保存用の配列
let undoDataStack = [];
let redoDataStack = [];

// canvasへの描画処理を行う前に行う処理
function beforeDraw() {
    // やり直し用スタックの中身を削除
    redoDataStack = [];
    // 元に戻す用の配列が最大保持数より大きくなっているかどうか
    if (undoDataStack.length >= STACK_MAX_SIZE) {
        // 条件に該当する場合末尾の要素を削除
        undoDataStack.pop();
    }
    // 元に戻す配列の先頭にcontextのImageDataを保持する
    undoDataStack.unshift(ctx.getImageData(0, 0, $canvas[0].width(), $canvas[0].height()));
}

function undo () {
     // 戻す配列にスタックしているデータがなければ処理を終了する
    if (undoDataStack.length <= 0) return;
    // やり直し用の配列に元に戻す操作をする前のCanvasの状態をスタックしておく
    redoDataStack.unshift(ctx.getImageData(0, 0, $canvas[0].width(), $canvas[0].height()));
    // 元に戻す配列の先頭からイメージデータを取得して
    var imageData = undoDataStack.shift();
    // 描画する
    ctx.putImageData(imageData, 0, 0);
}

function redo () {
    // やり直し用配列にスタックしているデータがなければ処理を終了する
    if (redoDataStack.length <= 0) return;
    // 元に戻す用の配列にやり直し操作をする前のCanvasの状態をスタックしておく
    undoDataStack.unshift(ctx.getImageData(0, 0, $canvas[0].width(), $canvas[0].height()));
    // やり直す配列の先頭からイメージデータを取得して
    var imageData = redoDataStack.shift();
    // 描画する
    ctx.putImageData(imageData, 0, 0);
}

関連リファレンス

https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/getImageData
https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/putImageData
https://developer.mozilla.org/ja/docs/Web/API/ImageData