오늘 해볼 것은 canvas 태그를 이용해 간단한 그림판 구현하기다.
크게는 펜과 지우개 버튼을 두고,
그 안에 크기 조정, 펜 색깔 변경, 모두 지우기 기능을 추가했다.
HTML <canvas> 태그는 주로 그래픽 콘텐츠를 그릴 때 사용되는 일종의 컨테이너이다.
실제로 그림을 그리는 동작은 스크립트를 통해 구현할 수 있다.
지원하는 브라우저 및 버전은 아래와 같다.
<canvas> 요소를 지원하지 않는 브라우저의 경우 태그 내부에 들어있는 텍스트를 노출한다.

fillStyle, fillRect 등 다양한 함수를 이용해 원하는 기능을 구현해 볼 수 있겄다.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 150, 100);
큼지막한 구조는 아래와 같습니다.
React 와 Typescript 환경이다.
const canvasRef = useRef<HTMLCanvasElement>(null);
...
return (
<CanvasWrapper>
<canvas ref={canvasRef} width={720} height={480} />
{isPenModalOpen && (
<PenModal />
)}
{isEraserModalOpen && (
<EraserModal />
)}
<ButtonWrapper>
<Button type="button" onClick={handlePenButtonClick} isClicked={isPenModalOpen}>펜
</Button>
<Button type="button" onClick={handleEraserButtonClick} isClicked={isEraserModalOpen}>지우개
</Button>
</ButtonWrapper>
</CanvasWrapper>
);
사용한 함수는 strokeStyle, lineWidth, beginPath, moveTo, lineTo, stroke, clearRect.
useEffect(() => {
// Canvas 요소에 대한 참조를 가져옵니다.
const canvas = canvasRef.current;
// Canvas 요소가 존재하지 않으면 종료합니다.
if (!canvas) return;
// Canvas의 2D context를 가져옵니다.
const ctx = canvas.getContext("2d");
// 2D context가 존재하지 않으면 종료합니다.
if (!ctx) return;
// 선 색상과 선 두께를 설정합니다.
ctx.strokeStyle = penColor;
ctx.lineWidth = mode === "pen" ? penSize : eraserSize;
// 그리기를 시작하는 함수입니다.
const startDrawing = (e: MouseEvent) => {
setIsDrawing(true);
const { offsetX, offsetY } = e;
ctx.beginPath();
ctx.moveTo(offsetX, offsetY);
};
// 마우스 이동 중에 그리는 함수입니다.
const onMouseMove = (e: MouseEvent) => {
if (!isDrawing) return;
const { offsetX, offsetY } = e;
if (mode === "pen") {
ctx.lineTo(offsetX, offsetY);
ctx.stroke();
} else {
ctx.clearRect(offsetX, offsetY, ctx.lineWidth, ctx.lineWidth);
}
};
// 그리기를 멈추는 함수입니다.
const stopDrawing = () => {
setIsDrawing(false);
};
// 마우스 이벤트 리스너를 추가합니다.
canvas.addEventListener("mousedown", startDrawing);
canvas.addEventListener("mousemove", onMouseMove);
canvas.addEventListener("mouseup", stopDrawing);
canvas.addEventListener("mouseout", stopDrawing);
// clearAll 상태가 true이면 Canvas를 모두 지웁니다.
if (clearAll) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
setClearAll(false);
}
// 메모리 누수와 불필요한 리소스 사용을 막기 위해 이벤트 리스너를 제거합니다.
return () => {
canvas.removeEventListener("mousedown", startDrawing);
canvas.removeEventListener("mousemove", onMouseMove);
canvas.removeEventListener("mouseup", stopDrawing);
canvas.removeEventListener("mouseout", stopDrawing);
};
}, [isDrawing, penSize, penColor, clearAll]);
사용된 함수에 대해 간단히들 살펴보자.
선의 색상과 두께를 설정하는 함수다.
ctx.strokeStyle = "red"; 는 선의 색상을 빨간색으로 지정하며,
ctx.lineWidth = 2; 는 선의 두께를 2px로 지정한다.
beginPath() 를 호출하여 새로운 선이나 도형을 그리기 전 새 경로를 만들어주고,
선을 그리기 시작할 점을 지정한다.
예를 들어, ctx.moveTo(100, 100); 은 선을 (100, 100) 위치에서 시작하도록 만든다.
선을 그리는 끝점을 지정한다.
moveTo로 지정한 시작점부터 해당 점까지 선이 그려진다.
마우스가 이동하면서 lineTo 가 갱신되며 점이 이어지는 식이다.
지정한 점까지 실제로 선을 화면에 그리는 함수인 stroke 가 실행된다.
지정된 사각형 영역을 지운다.
ctx.clearRect(x, y, width, height); 는 (x, y) 위치부터 width와 height 만큼의 영역을 지운다.
모두 지우기는 ctx.clearRect(0, 0, canvas.width, canvas.height); 코드로 간단히 구현할 수 있겄다.
언급한 기능 외에 ColorPicker 나 펜 질감 바꾸기 등의 기능을 추가해볼수도 있겄다.
생각한 것보다 구현에 오랜 시간을 썼는데, 간단히 정리해뒀으니 담엔 좀 더 수월할지도 ~
https://www.tcpschool.com/html-tags/canvas
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API