如何使用 Vue 在 Canvas 上实现撤销/重做?
如需使用 Vue 实现撤销/重做功能,无需使用 Konva 的序列化和反序列化方法。
只需保存应用内所有 state 变化的历史记录。实现方法有很多。如果使用不可变数据结构,此操作可能更简单。
操作说明:尝试通过拖动移动正方形。然后使用“undo”和“redo”按钮撤销或重做操作。
<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 });
// We use refs to keep history to avoid unnecessary re-renders
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) => {
// Remove all states after current step
history.value = history.value.slice(0, historyStep.value + 1);
const pos = {
x: e.target.x(),
y: e.target.y()
};
// Push the new state
history.value = history.value.concat([pos]);
historyStep.value += 1;
position.x = pos.x;
position.y = pos.y;
};
</script>
此示例展示如何:
- 使用 Vue 响应式系统记录位置历史
- 通过在历史记录中导航来实现撤销/重做功能
- 在拖动结束时更新历史记录
- 对当前位置使用
reactive,对历史记录使用ref,以保持响应性
请注意,我们使用 Vue 响应式系统管理 state,但会将历史记录保存在 ref 中,以避免不必要的重新渲染。
手动构建历史记录的局限
上述历史记录每一步只记录一个值。生产级编辑器必须记录 分组操作,使多选拖动可以作为一个步骤撤销,还必须记录 变换,以及在操作后才完成加载的图像。这个状态机 通常会变得比绘图代码更大,因此请围绕文档操作而不是原始节点状态来设计历史记录。