[React] Canvas 드로잉 히스토리 구현

아홉번째태양·2023년 7월 16일
0

canvas에서 ctrl+z 같은 undo 단축키를 사용할 수 있는 드로잉 히스토리는 canvas를 ImageData로 저장하고 이를 리스트로 보관하여 구현할 수 있다.

const canvasHistory = useRef<ImageData[]>([]);
const historyIndex = useRef<number>(-1);

useRef를 이용해 먼저 ImageData를 푸시할 리스트를 만든다.

그리고 히스토리의 기록과 undo로 불러올 히스토리를 지정하기 위해 index를 도입하였는데, 이러한 인덱스가 없이 그냥 push, pop만으로 구현할 경우 현재 canvas의 ImageData를 별도의 공간에 저장해야하거나, undo를 할 때 pop하려는 ImageData가 현재 ImageData가 아닌지 구분하는 로직이 따로 필요하다.

다음으로, push와, undo를 수행하는 함수를 만든다.

const pushHistory = () => {
  const context = contextRef.current;
  if (context === null) return;

  const { dpr, width, height } = size;
  const img = context.getImageData(0, 0, width * dpr, height * dpr);
  historyIndex.current += 1;
  canvasHistory.current[historyIndex.current] = img;
}

const undo = () => {
  const context = contextRef.current;
  if (context === null) return;
  
  const { dpr, width, height } = size;
  const img = canvasHistory.current[historyIndex.current - 1];
  if (img === undefined) return;

  context.putImageData(img, 0, 0, 0, 0, width * dpr, height * dpr);
  historyIndex.current -= 1;
}

히스토리를 보관할때는,
getImageData -> index 증가 -> 히스토리에 푸시
반대로 히스토리를 꺼낼때는,
lastIndex -1 참조 -> putImageData -> index 감소

undo 함수를 필요로하는 버튼에 onClick 추가해주거나, 아니면 페이지에 이벤트리스너를 추가해서 단축키를 인식하도록 한다.

useWindowEventListener('keydown', (e) => {
  if (e.key === 'z' && (e.ctrlKey || e.metaKey)) undo();
});

export const useWindowEventListener = <K extends keyof WindowEventMap>(
  type: K,
  listener: (this: Window, ev: WindowEventMap[K]) => void,
  options?: boolean | AddEventListenerOptions,
) => {
  useEffect(() => {
    window.addEventListener(type, listener, options);
    return () => {
      window.removeEventListener(type, listener, options);
    };
  }, [type, listener, options]);
};

0개의 댓글