이전 포스트에서는 React, TypeScript 환경에서 Fabric.js 캔버스를 생성하는 방법에 대해서 알아보았습니다. 이번 포스트에서는 캔버스의 다양한 속성 값을 동적으로 설정하는 방법에 대해 알아보겠습니다.
캔버스를 생성할 때 드로잉 모드를 미리 true
로 작성하지 않은 이상 캔버스의 드로잉 모드 기본값은 false
입니다.
const newCanvas = new fabric.Canvas('canvas', {
width: 500,
height: 500,
isDrawingMode: true
});
위의 코드처럼 isDrawingMode
의 값을 true
나 false
(isDrawingMode
값을 작성하지 않으면 기본으로 false
)로 캔버스를 생성할 수 있습니다. 만약 isDrawingMode
가 true
로 초기에 설정되어 있다면 캔버스에 그림을 그릴 수 있는 브러쉬가 자동으로 생성됩니다.
하지만 드로잉 모드를 동적으로 변경하고 싶을 때가 있습니다.
제 프로젝트를 예로 들면, 브러쉬 패널을 클릭했을 때만 드로잉 모드가 true
이고 다른 패널에서는 드로잉 모드가 false
여야 합니다. 왜냐하면 모든 패널에서 드로잉 모드가 true
라면 배경을 바꾸는데 불필요하게 그림이 그려질 수도 있기 때문입니다.
이럴 때는 useEffect를 활용해야 한다는 것을 잘 알고 계실 겁니다. 그렇다면 코드를 작성해 봅시다.
useEffect(() => {
if (canvas) {
canvas.isDrawingMode = nowPanel === 'brush';
}
}, [canvas, nowPanel]);
null
이나 undefined
일수도 있기 때문에 꼭 확인하는 과정이 있어야 합니다.nowPanel
은 현재 클릭된 패널을 의미합니다.nowPanel
이 brush
일 때만 isDrawingMode
를 true
라고 설정하였습니다.하지만 이렇게 작성해도 제대로 작동하지 않는다는 것을 확인할 수 있습니다. 😫😥😭 왜 이런 일이 발생할까요?
이유는 바로 Fabric은 자동으로 캔버스를 렌더링하지 않기 때문입니다. 이는 성능과 관련이 있습니다. Fabric은 캔버스에 다양한 개체와 애니메이션 등을 추가할 수 있습니다. 하지만 캔버스의 모든 개체와 애니메이션이 리렌더링을 하려고 한다면 성능에 매우 안 좋은 영향을 끼칠 수 있습니다. 그래서 Fabric은 수동으로 렌더링을 할 수 있는 메서드 renderAll
을 제공합니다.
공식 문서에서 확인할 수 있듯이 renderAll
메서드는 캔버스와 캔버스 안에 있는 컨테이너를 모두 렌더링합니다. 그래서 캔버스에 도형, 이미지를 추가하거나 속성 값을 바꿀 때 마지막에 꼭 renderAll
메서드를 작성해 주어야 변경 사항이 제대로 반영이 됩니다.
이제 코드를 고쳐봅시다.
useEffect(() => {
if (canvas) {
canvas.isDrawingMode = nowPanel === 'brush';
canvas.renderAll();
}
}, [canvas, nowPanel]);
true
로 변경하고 renderAll
메서드를 실행합니다.위처럼 코드를 변경하면 아래와 같이 브러쉬 패널에서 정상적으로 브러쉬 기능이 작동하는 것을 확인할 수 있습니다.
해당 프로젝트에서는 스티커 패널에서 원하는 스티커를 선택하면 캔버스에 스티커가 추가되고 스티커를 이동, 크기와 방향 조절, 삭제 등을 할 수 있습니다. 브러쉬 기능과 마찬가지로 스티커 기능도 다른 패널에서는 스티커 조작을 하지 못하도록 하려면 어떻게 해야 할까요? 바로 selectable
속성을 사용하면 됩니다.
isDrawingMode
처럼useEffect
에서selectable
을false
로 바꿔주면 되지 않나요?
라고 하실 수도 있습니다. 비슷하지만 조금 다릅니다.
브러쉬 기능의 isDrawingMode
는 캔버스에 그림을 그릴 수 있는 모드를 활성화 하냐 안하냐를 선택하는 속성입니다. 때문에 조금은 쉽게 isDrawingMode
속성을 동적으로 조작할 수 있습니다.
반면, 스티커 기능은 스티커를 누를 때마다 캔버스에 이미지를 추가합니다. 이때(이미지를 추가할 때), selectable
속성을 true
나 false
로 할 수 있습니다.
기본값은 false
이고 true
라고 하면 이미지의 크기와 방향을 조절하고 이동할 수 있는 선택 영역이 자동으로 생깁니다. (위 이미지에서 파란색 부분)
코드로 자세히 살펴봅시다.
fabric.Image.fromURL(url, (img) => {
const newImg = img.set({ selectable: true });
canvas.add(newImg);
canvas.renderAll();
});
selectable
여부를 결정할 수 있습니다.이미지의 selectable
여부를 동적으로 결정하고 싶을 때는 어떻게 해야할까요? 위에서 언급했던 것처럼 단순히 useEffect
에서 selectable
을 true
나 false
로 변경하면 제대로 동작하지 않습니다.
왜냐하면 위의 이미지를 추가하는 코드는 여러 개의 이미지 중 하나를 선택해 선택한 하나의 이미지만을 캔버스에 추가하는 코드이기 때문입니다. 다시 말해, 위의 코드는 이미지 하나만을 관리하는 코드입니다. 때문에 여러 개의 스티커를 추가한 상황에서 다른 패널로 이동하면 생각한대로 동작하지 않습니다.
방법은 여러 개의 스티커의 selectable
속성을 한 번에 처리해야 합니다. 여기서 fabric에서 제공하는 다양한 메서드를 사용할 수 있습니다.
캔버스에 이미지를 추가하면 해당 이미지는 객체로 관리됩니다. fabric의 getObjects()
메서드를 사용하면 캔버스의 모든 객체를 불러올 수 있습니다.
공식 문서를 보면 객체의 배열을 반환한다고 나와있습니다. 때문에 배열을 돌면서 개별 객체의 selectable
속성을 변경해주면 됩니다.
코드로 확인해 봅시다.
useEffect(() => {
if (canvas) {
const objects = canvas.getObjects();
for (const obj of objects) {
obj.selectable = nowPanel === 'sticker';
}
canvas.renderAll();
}
}, [canvas, nowPanel]);
objects
변수에 캔버스의 모든 객체 배열을 담습니다.for ... of
로 objects
배열을 돌면서 객체의 selectable
을 현재 패널이 'sticker'일때만 true
가 되도록 하였습니다.위처럼 코드를 작성하면 스티커 패널일 때만 스티커를 조작할 수 있습니다.
그림을 그린 후 실행 취소와 다시 실행을 할 수 있는 버튼을 만들어 보겠습니다. 감사합니다.
출처
🔗 Fabric.js 공식 홈페이지: http://fabricjs.com/