react typescript 로 그림판 만들기
문제
이번에 구현해야 하는 기능 중에서 그림판이 있었다..
HTML에 canvas Element가 있는데, 그걸 한번도 사용해본 적이 없었고 어떤 흐름으로 사용되는지 몰랐음.
DOM에 직접적으로 접근해야하고 mouse 이벤트를 사용해야함.
시도
일단 canvas 쓰고 본다. 그리고 getElementById 대신 react의 훅인 useRef를 사용하여 캔버스를 조작해준다.
최대한 addEventListener도 안쓰고 싶은 마음에 어떻게할지 고민하다가, react에서는 canvas 태그에 onMouseUp 등등을 사용할 수 있다는 사실을 알게되었다.
해결
const canvasRef = useRef<HTMLCanvasElement>(null);
// return 이하
<canvas
ref={canvasRef}
height={700}
width={700}
onMouseDown={mouseDownHandler}
onMouseMove={mouseMoveHandler}
onMouseUp={mouseUpHandler}
onMouseLeave={mouseLeaveHandler}
/>
마우스 핸들링 함수들
// useEffect + AddEventListener 대체 함수
const mouseDownHandler = (
event: React.MouseEvent<HTMLCanvasElement>
)=> {
startPaint(event);
};
const mouseMoveHandler = (
event: React.MouseEvent<HTMLCanvasElement>
)=> {
paint(event);
};
const mouseUpHandler = (event: React.MouseEvent<HTMLCanvasElement>)=>
{
exitPaint();
};
const mouseLeaveHandler = (
event: React.MouseEvent<HTMLCanvasElement>
)=> {
exitPaint();
}
};
마우스 위치를 구하는 함수
// 좌표 함수
const getCoordinates = (
event: React.MouseEvent<HTMLCanvasElement>
): Coordinate | undefined => {
if (!canvasRef.current) {
return;
}
const canvas: HTMLCanvasElement = canvasRef.current;
return {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop,
};
};
그림그릴때 사용되는 함수들 (custom hook으로 빼줬다)
// canvas에 선긋는 함수
const drawLine = (
originalMousePosition: Coordinate,
newMousePosition: Coordinate,
color: string
) => {
if (!ref.current) {
return;
}
const canvas: HTMLCanvasElement = ref.current;
const context = canvas.getContext("2d");
if (context) {
context.strokeStyle = black;
context.lineJoin = "round";
context.lineWidth = 5;
context.beginPath();
context.moveTo(originalMousePosition.x, originalMousePosition.y);
context.lineTo(newMousePosition.x, newMousePosition.y);
context.closePath();
context.stroke();
}
};
// mouse event 에 따른 처리 함수
const startPaint = useCallback(
(event: React.MouseEvent<HTMLCanvasElement>) => {
const coordinates = action(event);
if (coordinates) {
setIsPainting(true);
setMousePosition(coordinates);
}
},
[]
);
const paint = useCallback(
(event: React.MouseEvent<HTMLCanvasElement>)=> {
event.preventDefault(); // prevent drag
event.stopPropagation(); // prevent drag
if (isPainting) {
const newMousePosition = action(event);
if (mousePosition && newMousePosition) {
drawLine(mousePosition, newMousePosition, color);
setMousePosition(newMousePosition);
}
}
},
[isPainting, mousePosition]
);
const exitPaint = useCallback(() => {
setIsPainting(false);
}, []);