노마드 코더 바닐라JS로 그림 앱 만들기 강의를 보며 진행했던 프로젝트를 React로 바꿔보고싶었다. 예전에 한 번 시도했었다가 막힌 이후로 다시 한 번 시도해보는 것이다.
국비 과정을 통해 다양한 React 프로젝트를 경험하다보니, 예전에 왜 실패했었는지 단번에 이해가 되었다. 옛날 실패했던 프로젝트 코드를 열어보니, 그때는 JavaScript에만 익숙했기 때문에 React App 위에 JavaScript를 얹혀놓은 괴상한 모습을 하고 있었다.
바닐라JS와는 다르게, React에서는 canvas와 getContext()를 사용하기 위해서는 먼저 useRef()와 useEffect(), useState()의 도움을 받아야 한다.
<body>
<canvas id="canvas"></canvas>
</body>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
</script>
import React { useEffect, useRef, useState } from "react";
export default function App() {
const canvasRef = useRef(null);
const [getCtx, setGetCtx] = useState(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
setGetctx(ctx);
}, [])
return (
<canvas ref={canvasRef}></canvas>
);
}
canvas에 대한 설정을 해준 다음에는, canvas 범위 내에서 현재 마우스의 위치 정보를 받아와야 한다. 그래야 그 위치에 그림을 그릴 수 있기 때문이다. 바닐라JS에서는 addEventListener()를 사용했지만, React에서는 canvas 태그에 Mouse Event를 사용하자.
<script>
canvas.addEventListener("mousemove", onMouseMove);
canvas.addEventListener("mousedown", startPainting);
canvas.addEventListener("mouseup", stopPainting);
canvas.addEventListener("mouseleave", stopPainting);
</script>
return(
<canvas
ref={canvasRef}
onMouseDown={() => setPainting(true)}
onMouseUp={() => setPainting(false)}
onMouseMove={e => drawFn(e)}
onMouseLeave={() => setPainting(false)}
>
</canvas>
);
위의 이벤트를 설명하자면, 마우스를 눌렀을 경우 그림을 그릴 수 있고, 마우스를 뗐을 경우나 canvas 범위 내에서 벗어날 때에는 그림을 그릴 수 없다. 그리고 마우스를 canvas 범위 안에서 움직일 때 움직인 위치 정보를 가져다주게 된다.
바닐라 JS는 offsetX와 offsetY로 위치 정보를 가져올 수 있는데, React에서는 nativeEvent로 한 번 거쳐서 offsetX와 offsetY를 가져와야 한다. clientX나, pageX 등은 canvas 범위를 기준으로 하지 않기 때문에 내가 그림을 그리려는 위치와 실제 그림이 그려지는 위치가 차이가 발생한다. 따라서 React에서는 event.nativeEvent.offsetX, event.nativeEvent.offsetY와 같이 사용해야 한다.
<script>
let painting = false;
function stopPainting() {
painting = false;
}
function startPainting() {
painting = true;
}
function onMouseMove(event) {
const x = event.offsetX;
const y = event.offsetY;
if(!painting) {
ctx.beginPath();
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
ctx.stroke();
}
}
</script>
const [painting, setPainting] = useState(false);
const drawFn = e => {
const mouseX = e.nativeEvent.offsetX;
const mouseY = e.nativeEvent.offsetY;
if (!painting) {
getCtx.beginPath();
getCtx.moveTo(mouseX, mouseY);
} else {
getCtx.lineTo(mouseX, mouseY);
getCtx.stroke();
}
}
// react
import React, { useRef, useEffect, useState } from "react";
// style
import { CanvasStyle } from "./styles/cavas";
export default function App() {
// useRef
const canvasRef = useRef(null);
// getCtx
const [getCtx, setGetCtx] = useState(null);
// painting state
const [painting, setPainting] = useState(false);
useEffect(() => {
// canvas useRef
const canvas = canvasRef.current;
canvas.width = 650;
canvas.height = 540;
const ctx = canvas.getContext("2d");
ctx.lineJoin = "round";
ctx.lineWidth = 2.5;
ctx.strokeStyle = "#000000";
setGetCtx(ctx);
}, []);
const drawFn = e => {
// mouse position
const mouseX = e.nativeEvent.offsetX;
const mouseY = e.nativeEvent.offsetY;
// drawing
if (!painting) {
getCtx.beginPath();
getCtx.moveTo(mouseX, mouseY);
} else {
getCtx.lineTo(mouseX, mouseY);
getCtx.stroke();
}
}
return (
<CanvasStyle>
<div className="view">
<div className="canvasWrap">
<canvas
className="canvas"
ref={canvasRef}
onMouseDown={() => setPainting(true)}
onMouseUp={() => setPainting(false)}
onMouseMove={e => drawFn(e)}
onMouseLeave={() => setPainting(false)}
>
</canvas>
</div>
</div>
</CanvasStyle>
)
}

일단 검은 선만 그을 수 있는 상태다. 다음번에는 노마드 코더 바닐라JS로 그림 앱 만들기 강의 내용처럼 색상선택이나 페인트 기능, 저장 기능 등을 구현해야하는데...(귀찮)
시간 날 때마다 계속 작업해보도록 하겠다.
끝.
다음 글 : React로 그림판 만들기2 : 리셋&저장
잘봤습니다