생성을 또 하게될 수도 있으니까
useHover 훅 만들었던 것처럼 ref를 이용한다
캔버스를 생성하고 생성된 캔버스 요소의 ref를 반환하자
import { useEffect, useRef } from 'react';
interface UseCanvasProps {
setCanvas: (canvas: HTMLCanvasElement) => void;
}
const useCanvas = ({ setCanvas }: UseCanvasProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
setCanvas(canvas);
}, []);
return canvasRef;
};
export default useCanvas;
이렇게 호출한다
const canvasRef = useCanvas({
**setCanvas:** (canvas: HTMLCanvasElement) => {
canvas.width = 200;
canvas.height = 200;
canvas.style.background = 'pink';
},
});

화면이 뿅 등장~
interface ICarCoord {
x: number;
y: number;
}const carImageRef = useRef<HTMLImageElement | **null**>(null); [(useRef) Cannot assign to 'current' because it is read-only propertyconst car1Ref = useRef<ICarCoord>({ x: 0, y: 0 }); 우선, 차 한대에 대해서만 생각해보자!! (추후엔 최대 8대가 되야함 !)이제 본격적으로 자동차를 불러오고 캔버스에서 움직이는 걸 구현한다!
자동차의 위치(서버로부터 받을 데이터에 의함)에 따라 변경될 값이니까 모두 useEffect 안에서 관리한다!!!
** 이 글에선 useEffect로 감싸지 않고 내부 코드만 작성해둘 것이다.
if (car1) {
const img = new Image();
img.src = car1;
carImageRef.current = img;
car1Ref.current = { x: 20, y: 20 }; // 자동차 초기 좌표 배정
}
애니메이션을 이용하기 위해 requestAnimationFrame을 사용해보자
const carImageRef = useRef<HTMLImageElement | null>(null);
const car1Ref = useRef<ICarCoord>({ x: 0, y: 0 });
const loadImage = useCallback(
(src: string) =>
new Promise<HTMLImageElement>((resolve) => {
const img = new Image();
img.src = src;
img.sizes = '2';
img.onload = () => resolve(img);
}),
[]
);
useEffect(() => {
loadImage(car1).then((img) => {
carImageRef.current = img;
car1Ref.current = { x: 20, y: 20 };
});
let rafTimer: number | undefined;
const cvs = canvasRef.current;
const ctx = cvs?.getContext('2d');
if (!ctx) {
return;
}
const animate = () => {
const car = carImageRef.current;
if (car) {
ctx.drawImage(car, 100, 10); // 임의 지정 위치!
}
rafTimer = requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
});

작소 자동차 등장!!!!
drawImage(image, dx, dy)
drawImage(image, dx, dy, dWidth, dHeight)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, **dWidth, dHeight)**
ctx.drawImage(car, 100, 10); 를 수정해서 위치를 업데이트하면, 자동차가 계속 쌓인다 ?!
→ 이전 작업을 지워줘야한다!
ctx?.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // 이전 값 삭제
자동차 좌표 x,y 값을 바꾸어야 한다.
자동차의 x,y 좌표를 각각 30씩 이동시키는 함수를 만든다.
const updateCarCoord = useCallback(
(carCoord: ICarCoord) => {
carCoord.x += 30;
carCoord.y += 30;
blockOverflowPos(carCoord);
},
[blockOverflowPos]
);
바뀐 좌표가 canvas 영역을 벗어나는지 확인하는 함수도 적용한다.
const blockOverflowPos = useCallback((pos: ICarCoord) => {
pos.x = pos.x >= 1100 ? 1000 : pos.x < 0 ? 0 : pos.x;
pos.y = pos.y >= 450 ? 400 : pos.y < 0 ? 0 : pos.y;
}, []);
이들을 사용해서 애니메이션을 그리는 useEffect 내부를 수정해본다.
**const coord = car1Ref.current; ///<<- 차 좌표에 대한 참조변수의 현재 값을 가져오고**
const animate = () => {
const car = carImageRef.current;
if (car) {
**updateCarCoord(coord); // <<<- 그 좌표를 갱신한다.**
ctx.drawImage(car, coord.x, coord.y); // 위치!px단위
}
rafTimer = requestAnimationFrame(animate); // <<<- 이로인해 그려짐!!
};
requestAnimationFrame(animate);
좌표가 바뀌어 차가 이동하는 것을 그리기 위해 테스트용 setInterval을 추가했다.
setInterval(() => {
updateCarCoord(car1Ref.current);
}, 5000);
animate 코드를 보면
**const animate = () => {**
const car = carImageRef.current;
console.log('render..', coord);
if (car) {
// updateCarCoord(coord);
ctx.drawImage(car, coord.x, coord.y); // 위치!px단위
}
**rafTimer = requestAnimationFrame(animate); ////////**
};
**requestAnimationFrame(animate);**
이렇게 animate를 재귀호출한다!!
그래서 콘솔에 위의 ‘render..’가 계~~속 찍힌다.
return () => {
rafTimer && cancelAnimationFrame(rafTimer);
rafTimer = undefined;
};
useEffect 내의 코드를 정리(clean-up)하기 위함이다!
deps가 비어있다면 컴포넌트가 사라질 때 호출된다.
특정 값이 있다면, 그 값이 바뀌여서 이전 effect가 필요없을때! 즉 새로운값으로 바뀌면서 그 때 그전거를 청소