VueでKonva.jsとcanvasを使ってお絵描き(その4)


その3はこちら

戻る および やり直しの処理を実装(undo, redo)

キャンバスの状態を1つ前の状態に戻したり(undo)、やり直したり(redo)してみます。
ここを参考にしました。

描画時:描画する前に、描画前のキャンバスの状態を配列に保存しておく
戻す時:戻す前のキャンバスの状態をやり直し配列に保存し、戻す配列からキャンバスイメージを取り出して反映
やり直す時:やり直し前のキャンバスの状態を戻す配列に保存し、やり直し配列からキャンバスイメージを取り出して反映

※配列への保存と取り出しはFIFO方式で。
※キャンバスの状態はcontext.getImageDataメソッドで取得できるので、それを利用する
※context.putImageDataで、取り出したキャンバスの状態を反映する

親側(CallCanvas.vue)
メソッドを用意して、子の戻す、やり直すメソッドを読んでいるだけです。

CallCanvas.vue
<template>
    <div>
        <div class="md-layout md-gutter" style="margin-left: 340px">
          <div class="md-layout-item">
            ...
            ..
            .

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
                @click="onUndo" <!-- この行を追加 -->
              >
                戻る
              </md-button>
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
                @click="onRedo" <!-- この行を追加 -->
              >
                進む
              </md-button>
            </md-field>

            ...
            ..
            .
          </div>
        </div>
        ...
        ..
        .
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  ...
  ..
  .
  methods: {
    ...
    ..
    .
    // 元に戻す
    onUndo: function () {
      this.$refs.freeDrawing.undo()
    },
    // やり直す
    onRedo: function () {
      this.$refs.freeDrawing.redo()
    }
  },
  ...
  ..
  .
}
</script>
...
..
.

次は子です(FreeDrawing.vue)
undoメソッドとredoメソッドの新規追加に伴い、
dataプロパティに2つ追加 および mousedownメソッドとonClearCanvasメソッドに1行ずつ追加しています。

FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  data: () => ({
    ...
    ..
    .
    /** 追加 */
    undoDataStack: [], // 元に戻す配列
    redoDataStack: [] // やり直し配列
  }),
  ...
  ..
  .
  methods: {
    mousedown: function () {
      ...
      ..
      .
      // 戻す配列に描画前のキャンバスの状態を保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))
    },
    ...
    ..
    .
    onClearCanvas: function () {
      // キャンバスをクリアする前の状態を戻す配列に保存しておく
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      ...
      ..
      .
    },
    ...
    ..
    .
    /** 元に戻す */
    undo: function () {
      // 戻す配列が空の場合は何もしない
      if (this.undoDataStack.length <= 0) {
        return
      }

      // 戻す前の状態をやり直し配列に保存
      this.redoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      // 元に戻す(戻す配列からキャンバスの状態を取り出して反映)
      this.context.putImageData(this.undoDataStack.pop(), 0, 0)
      this.drawingLayer.draw()
    },
    /** やり直す */
    redo: function () {
      // やり直し配列が空の場合は何もしない
      if (this.redoDataStack.length <= 0) {
        return
      }

      // やり直す前の状態を戻す配列に保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      // やり直す(やり直し配列からキャンバスの状態を取り出して反映)
      this.context.putImageData(this.redoDataStack.pop(), 0, 0)
      this.drawingLayer.draw()
    }
  },
  ...
  ..
  .
}
</script>

いざ、戻してみます。

戻る1回目

戻る2回目

戻る3回目
※少しわかりづらいですが、「1」の下の棒?が消えてくれました

今度はやり直しを実行してみます(「進む」ボタンのこと)。
やり直し1回目

やり直し2回目

やり直し3回目

リセットして戻してみます。
リセット

戻る

無事、戻る処理とやり直す処理を実装できました。
モードの「直線」が未実装なので、次は直線を引いてみようと思います。
(多分最後。)

備考

リセットを連打すると、初期の令和だけ表示されてるキャンバス状態が配列にスタックされまくるので、
そこはよしなに処理を追加していただければと思います。

その5 直線を引けるようにしてみた