노마드 코더 바닐라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 : 리셋&저장
잘봤습니다