跳到主要内容

使用 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 撤销与重做示例