使用 React 和 Konva 实现 Canvas 撤销与重做
Canvas 像素不包含应用程序历史记录。请将历史记录存储在生成 Canvas 场景的 数据模型中。
对于小型编辑器,请在每项完整的用户操作后存 储不可变快照。 保留当前的历史记录索引。在撤销后进行新编辑时,请移除该索引之后的 快照。
不要在历史记录中存储 Konva 节点实例。请存储普通应用程序数据,例如 位置、颜色、文本和稳定标识符。
在 React 中使用快照历史记录
拖动矩形。每次完成拖动都会添加一个快照。Undo 和 Redo 按钮会更改当前的历史记录索引。
import { useRef, useState } from 'react';
import { Stage, Layer, Rect, Text } from 'react-konva';
const initialRectangle = {
id: 'rectangle-1',
x: 80,
y: 100,
width: 170,
height: 110,
fill: '#4dabf7',
};
const App = () => {
const history = useRef([initialRectangle]);
const historyIndex = useRef(0);
const [rectangle, setRectangle] = useState(initialRectangle);
const commit = (nextRectangle) => {
const previousSnapshots = history.current.slice(0, historyIndex.current + 1);
history.current = [...previousSnapshots, nextRectangle];
historyIndex.current = history.current.length - 1;
setRectangle(nextRectangle);
};
const undo = () => {
if (historyIndex.current === 0) {
return;
}
historyIndex.current -= 1;
setRectangle(history.current[historyIndex.current]);
};
const redo = () => {
if (historyIndex.current === history.current.length - 1) {
return;
}
historyIndex.current += 1;
setRectangle(history.current[historyIndex.current]);
};
return (
<>
<button onClick={undo} disabled={historyIndex.current === 0}>
Undo
</button>
<button
onClick={redo}
disabled={historyIndex.current === history.current.length - 1}
>
Redo
</button>
<Stage width={window.innerWidth} height={380}>
<Layer>
<Text x={20} y={20} text="Drag the rectangle" fontSize={18} />
<Rect
{...rectangle}
stroke="#1e3a5f"
strokeWidth={3}
cornerRadius={10}
draggable
onDragEnd={(event) => {
commit({
...rectangle,
x: event.target.x(),
y: event.target.y(),
});
}}
/>
</Layer>
</Stage>
</>
);
};
export default App;
每项用户操作只提交一个历史记录条目。不要为每次 指针移动添加条目。此规则使撤销行为可预测,并限制内存用量。
大型文档可能使完整快照的成本很高。在这种情况下,请存储命令 或补丁,并包含足够的数据以应用和撤销每项更改。此模型更 复杂,因此仅在测量结果表明快照存在限制后使用。
为长时间的编辑器会话定义历史记录上限。达到上限后,请移除最早的快照。 将保存的文档版本与本地 撤销历史记录分开保存。
如需更小的实现,请参阅专门的 React 撤销与重做示例。