跳到主要内容

如何使用 React 在 Canvas 上实现撤销/重做?

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

只需保存应用中所有 state 变更的历史记录。实现方式有很多种。使用不可变数据结构可以使此操作更简单。

操作说明:尝试拖动正方形。然后使用“撤销”和“重做”按钮还原或重放操作。

import React, { Component } from 'react';
import { Stage, Layer, Rect, Text } from 'react-konva';


const App = () => {
const [position, setPosition] = React.useState({ x: 20, y: 20 });
// We use refs to keep history to avoid unnecessary re-renders
const history = React.useRef([{ x: 20, y: 20 }]);
const historyStep = React.useRef(0);

const handleUndo = () => {
if (historyStep.current === 0) {
return;
}
historyStep.current -= 1;
const previous = history.current[historyStep.current];
setPosition(previous);
};

const handleRedo = () => {
if (historyStep.current === history.current.length - 1) {
return;
}
historyStep.current += 1;
const next = history.current[historyStep.current];
setPosition(next);
};

const handleDragEnd = (e) => {
// Remove all states after current step
history.current = history.current.slice(0, historyStep.current + 1);
const pos = {
x: e.target.x(),
y: e.target.y(),
};
// Push the new state
history.current = history.current.concat([pos]);
historyStep.current += 1;
setPosition(pos);
};

return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Text text="undo" onClick={handleUndo} />
<Text text="redo" x={40} onClick={handleRedo} />
<Rect
x={position.x}
y={position.y}
width={50}
height={50}
fill="black"
draggable
onDragEnd={handleDragEnd}
/>
</Layer>
</Stage>
);
};

export default App;

手动构建历史记录的局限

上述历史记录每一步只记录一个值。生产级编辑器必须记录 成组操作,使多选拖动可以作为一个步骤撤销,还必须记录 变换,以及操作完成后才加载完的图像。该状态机 通常比绘图代码更庞大,因此请围绕文档操作而不是原始节点状态来设计历史记录。