이전 포스트에서는 Fabric.js에서 동적으로 속성 값을 설정하는 방법에 대해서 알아보았습니다. 이번 포스트에서는 캔버스의 그림이나 이미지 등을 실행 취소하거나 다시 실행할 수 있는 버튼을 만들어 보겠습니다.
Fabric.js는 강력한 캔버스 라이브러리이지만 단점이 한 가지 있습니다. 바로 드로잉 모드일 때 사용할 수 있는 지우개 기능
이 없다는 것입니다. 그래서 그림을 그릴 수는 있지만 그림을 지울 수는 없습니다.
이미지 같은 경우에는 삭제 기능을 컨트롤 할 수 있는 deleteControl
이 존재하기 때문에 삭제 버튼을 구현하여 이미지를 삭제 할 수 있습니다. (자세한 내용은 공식 문서로 확인하세요.)
이러한 이유로 지우개 기능 대신 실행 취소와 다시 실행 버튼을 만들어 그림 그린 것을 취소하고 그렸던 것을 다시 불러오도록 할 것입니다. 원하는 부분만 지울 수는 없지만 비슷하게 지우개 기능을 구현할 수 있습니다.
먼저 실행 취소 버튼을 만들어 보겠습니다. 버튼을 생성한 후 버튼에 onClick 이벤트를 추가합니다.
const handleUndoClick = () => {
// ...
};
return (
<>
<button onClick={() => handleUndoClick()}></button>
</>
);
이제 실행 취소 버튼의 동작 원리를 생각해 봅시다. 실행 취소 버튼을 계속 누르면 가장 최근에 캔버스에 추가된 것부터 삭제됩니다. 그렇다면 캔버스에 추가된 순서와 내용을 모두 알아야 합니다.
const [isLocked, setIsLocked] = useState(false);
const [history, setHistory] = useState<fabric.Object[] | undefined>([]);
fabric.Object[]
로 설정해 주세요. history
는 실행 취소 버튼을 클릭할때마다 history 배열에 제일 마지막 객체가 저장됩니다.const saveHistory = () => {
if (!isLocked) {
setHistory([]);
}
setIsLocked(false);
};
useEffect(() => {
if (canvas) {
canvas.on('object:added', saveHistory);
canvas.on('object:modified', saveHistory);
canvas.on('object:removed', saveHistory);
}
}, [canvas]);
saveHistory
함수는 isLocked
가 false
일 때 setHistory
의 배열을 빈배열로 초기화합니다. isLocked
가 true
이면 isLocked
를 false
로 변경합니다.saveHistory
함수는 히스토리에 저장하는 것을 제어합니다.on
메서드로 이벤트를 설정할 수 있습니다.object:added
: 객체 추가 후 실행object:modified
: 객체 수정(이동, 확장, 회전) 후 실행object:removed
: 객체 삭제 후 실행saveHistory
함수를 실행합니다. 이제 버튼에 걸어 두었던 이벤트 함수를 작성해 봅시다.
const handleUndoClick = () => {
if (canvas) {
if (canvas._objects.length > 0){
const poppedObject = canvas._objects.pop();
setHistory((prev: fabric.Object[] | undefined) => {
if (prev === undefined) {
return poppedObject ? [poppedObject] : [];
}
return poppedObject ? [...prev, poppedObject] : prev;
});
canvas.renderAll();
}
}
};
_objects
에 저장되고 다양한 메서드를 활용하여 접근할 수 있습니다. canvas._objects.length
로 캔버스에 추가된 객체의 개수를 확인할 수 있습니다. 객체의 개수가 하나라도 있다면 history 상태 배열에 저장해야 합니다.canvas._objects.pop()
으로 제일 마지막 객체를 꺼내 poppedObject
변수에 저장합니다.setHistory
로 꺼낸 객체를 저장합니다.renderAll()
메서드로 캔버스를 렌더링해줍니다.canvas._objects
에 있던 객체들 중 아까 꺼냈던 마지막 객체를 뺀 나머지 객체들이 캔버스에 다시 렌더링됩니다.크게 흐름을 다시 정리해봅시다.
객체 추가, 수정, 삭제 이벤트 발생 시 ➡️ saveHistory 함수 실행
'실행 취소' 버튼 누르면 ➡️ _objects의 맨 마지막 객체 꺼냄 ➡️ history에 저장 ➡️ renderAll() ➡️ 캔버스에 마지막 객체 뺀 나머지 객체들이 렌더링
다시 실행은 버튼을 눌렀을 때 실행 취소했던 것들을 다시 불러와야 합니다. 이를 위해 history와 isLocked 상태가 필요하였습니다. 실행 취소와 마찬가지로 버튼을 생성 후 onClick으로 이벤트를 추가합니다.
const handleRedoClick = () => {
if (canvas && history) {
if (history.length > 0) {
setIsLocked(true);
canvas.add(history[history.length - 1]);
const newHistory = history.slice(0, -1);
setHistory(newHistory);
}
}
};
add
메서드로 history의 가장 마지막 객체를 canvas에 추가합니다.흐름을 다시 정리해봅시다.
'다시 실행' 버튼 클릭 ➡️ isLocked = true, 캔버스에 history의 가장 마지막 객체가 추가 ➡️ 가장 마지막 객체를 뺀 나머지 객체 배열을 history에 다시 업데이트
완성하면 이런 모습입니다!
참고
🔗Fabric.js 이벤트: https://github.com/fabricjs/fabric.js/wiki/Working-with-events