Skip to main content

如何在 Vue 中实现画布的撤销/重做功能?

在 Vue 中实现撤销/重做功能时,不需要使用 Konva 的序列化和反序列化方法。

你只需保存应用中所有状态变化的历史记录。实现方式有很多,如果使用不可变数据结构,可能会更简单。

说明:尝试拖动方块移动它。然后使用“撤销”和“重做”按钮来回退或重现你的操作。

<template>
  <v-stage :config="stageSize">
    <v-layer>
      <v-text
        :config="{
          text: 'undo',
          x: 10,
          y: 10
        }"
        @click="handleUndo"
      />
      <v-text
        :config="{
          text: 'redo',
          x: 50,
          y: 10
        }"
        @click="handleRedo"
      />
      <v-rect
        :config="{
          x: position.x,
          y: position.y,
          width: 50,
          height: 50,
          fill: 'black',
          draggable: true
        }"
        @dragend="handleDragEnd"
      />
    </v-layer>
  </v-stage>
</template>

<script setup>
import { ref, reactive } from 'vue';

const stageSize = {
  width: window.innerWidth,
  height: window.innerHeight
};

const position = reactive({ x: 20, y: 20 });
// 我们使用 ref 来保存历史,避免不必要的重新渲染
const history = ref([{ x: 20, y: 20 }]);
const historyStep = ref(0);

const handleUndo = () => {
  if (historyStep.value === 0) {
    return;
  }
  historyStep.value -= 1;
  const previous = history.value[historyStep.value];
  position.x = previous.x;
  position.y = previous.y;
};

const handleRedo = () => {
  if (historyStep.value === history.value.length - 1) {
    return;
  }
  historyStep.value += 1;
  const next = history.value[historyStep.value];
  position.x = next.x;
  position.y = next.y;
};

const handleDragEnd = (e) => {
  // 删除当前步骤后的所有状态
  history.value = history.value.slice(0, historyStep.value + 1);
  const pos = {
    x: e.target.x(),
    y: e.target.y()
  };
  // 推入新状态
  history.value = history.value.concat([pos]);
  historyStep.value += 1;
  position.x = pos.x;
  position.y = pos.y;
};
</script>

该示例演示了如何:

  1. 使用 Vue 的响应式系统跟踪位置历史
  2. 通过遍历历史记录实现撤销/重做功能
  3. 拖拽结束时更新历史记录
  4. 使用 reactive 管理当前位置,使用 ref 管理历史以保持响应性

请注意,我们使用 Vue 的响应式系统来管理状态,但通过将历史记录保存在 ref 中,避免了不必要的重复渲染。