일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 부스트캠프
- beautifulsoup
- 씨쁠쁠
- 자바 프로젝트
- 파이썬 코딩테스트
- 비디오 스트리밍
- git checkout
- c++
- 자바스크립트
- PubSub 패턴
- 파이썬
- React ssr
- 자바스크립트 객체
- 네이버 부스트캠프
- 스택
- react
- 웹크롤링
- Server Side Rendering
- React.js
- 파이썬 웹크롤링
- 네이버 부스트캠프 멤버십
- 코딩테스트
- Next.js
- Next/Image 캐싱
- 멘션 추천 기능
- 프로그래머스
- 브라우저 동작
- Image 컴포넌트
- 자바스크립트 컴파일
- 네이버 부캠
- Today
- Total
코린이의 개발 일지
[React] 리액트에서 Canvas API로 애니메이션 구현하기 본문
window.addeventListener를 써보자
맨처음에 이 방법으로 keyDown이벤트를 발생시켜 보았다. 리액트스럽게 훅으로만 대부분을 해결해서 코드를 짜는게 목표였지만 onKeyDown으로 도저히 작동을 안해서 (내가 잘못쓰고 있는 것였다.) 그냥 직접 이벤트 리스너를 붙여봤다. (리액트에서 권유하는 방법은 아니니 안쓰는 편이 좋다)
근데 문제가 발생했다.
이벤트 리스너를 렌더링 할 때마다 달아주면 리스너가 너무 많이 달리니까 useEffect안에 넣어뒀는데 그것도 문제였다.
리스너는 딱 한번만 호출되어야하는데 useEffect가 여러번 호출되면서 이벤트 리스너가 너무 많이 붙여지는게 문제였다.
해결한 코드는 다음과 같다.
import React, { RefObject, useRef, useEffect, useState } from 'react';
import { CanvasWrapper } from './style/AppStyle';
const boxInfo: { x: number; y: number; color: string } = {
x: 50,
y: 50,
color: 'blue',
};
const prevPosition: { x: number; y: number } = {
x: 100,
y: 100,
};
const move: number = 10;
function App(): JSX.Element {
const canvasRef: RefObject<HTMLCanvasElement> =
useRef<HTMLCanvasElement>(null); // 현재 태그
const [position, setPosition] = useState({ x: 100, y: 100 });
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null);
let raf: number;
function draw(): void {
if (ctx !== null) {
// console.log(position);
ctx.clearRect(prevPosition.x, prevPosition.y, boxInfo.x, boxInfo.y);
ctx.fillStyle = boxInfo.color;
ctx.fillRect(position.x, position.y, boxInfo.x, boxInfo.y);
raf = window.requestAnimationFrame(draw);
}
}
const handleKeyDown = (e: KeyboardEvent): void => {
switch (e.key) {
case 'ArrowRight':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x + move, y: position.y });
break;
case '37':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x - move, y: position.y });
break;
case 'ArrowUp':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x, y: position.y + move });
break;
case 'ArrowDown':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x, y: position.y - move });
break;
default:
break;
}
raf = window.requestAnimationFrame(draw);
};
useEffect(() => {
console.log('in useEffect');
const Canvas = canvasRef.current as HTMLCanvasElement;
setCtx(Canvas.getContext('2d'));
window.addEventListener('keydown', handleKeyDown);
if (ctx !== null) {
ctx.fillStyle = 'blue';
ctx.fillRect(position.x, position.y, boxInfo.x, boxInfo.y);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.cancelAnimationFrame(raf);
};
}, [ctx, position]);
return (
<>
<CanvasWrapper>
<canvas id="canvas" width="1500" height="1000" ref={canvasRef}></canvas>
</CanvasWrapper>
</>
);
}
export default App;
useEffect에 리턴 값을 주면 언마운트 될때 그 값들이 실행된다.
return 값으로 이벤트리스너를 제거해주니 정상적으로 동작하였다. 허나 앞서 말했듯 이 방법은 직접 돔을 건드리는 거라 그렇게 좋은 방법은 아니다.
다음은 onKeyDown 으로 해결한 코드이다.
import React, {
RefObject,
useRef,
useEffect,
useState,
KeyboardEvent,
} from 'react';
import { CanvasWrapper } from './style/AppStyle';
const boxInfo: { x: number; y: number; color: string } = {
x: 50,
y: 50,
color: 'blue',
};
const prevPosition: { x: number; y: number } = {
x: 100,
y: 100,
};
const move: number = 10;
function App(): JSX.Element {
const canvasRef: RefObject<HTMLCanvasElement> =
useRef<HTMLCanvasElement>(null); // 현재 태그
const [position, setPosition] = useState({ x: 100, y: 100 });
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null);
let raf: number;
const keyDownEvent = (e: KeyboardEvent<HTMLDivElement>): void => {
console.log('이벤트 전', position);
console.log('이벤트 전', e.key);
switch (e.key) {
case 'ArrowRight':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x + move, y: position.y });
console.log('이벤트 중', position);
break;
case '37':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x - move, y: position.y });
break;
case 'ArrowUp':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x, y: position.y + move });
break;
case 'ArrowDown':
prevPosition.x = position.x;
prevPosition.y = position.y;
setPosition({ x: position.x, y: position.y - move });
break;
default:
break;
}
raf = window.requestAnimationFrame(draw);
};
function draw(): void {
if (ctx !== null) {
// console.log(position);
ctx.clearRect(prevPosition.x, prevPosition.y, boxInfo.x, boxInfo.y);
ctx.fillStyle = boxInfo.color;
ctx.fillRect(position.x, position.y, boxInfo.x, boxInfo.y);
raf = window.requestAnimationFrame(draw);
}
}
useEffect(() => {
console.log('in useEffect');
const Canvas = canvasRef.current as HTMLCanvasElement;
setCtx(Canvas.getContext('2d'));
window.addEventListener('keyup', e => {
window.cancelAnimationFrame(raf);
});
if (ctx !== null) {
ctx.fillStyle = 'blue';
ctx.fillRect(position.x, position.y, boxInfo.x, boxInfo.y);
}
return () => window.removeEventListener("keydown", handleKeyUp);
}, [ctx, position]);
return (
<>
<CanvasWrapper>
<div onKeyDown={keyDownEvent} tabIndex={0}>
<canvas
id="canvas"
width="1500"
height="1000"
ref={canvasRef}
></canvas>
</div>
</CanvasWrapper>
</>
);
}
export default App;
이때 tabindex라는 걸 처음 들어봤다.
이 tabindex라는 것을 알기 위해선 키보드 포커스라는 걸 알아야한다.
보통 사이트에서 칸마다 뭘 입력할때 탭으로 이동이 가능한 것을 알 수 있는데 이것은 각각의 입력 태그들이 기본적으로 키보드 포커스가 잡히게 되어있기 때문이다.
이런 것들이 가능한 element들이 있는데 대표적을 input, select, button 같은 form태그와 a 태그이다.
상호작용하지 않는 div나 span 태그와 같은 요소에도 키보드 포커스가 잡히게 하고 싶을 경우에는 tabindex를 0으로 주면 된다.
tabindex를 -1로 주면 상호작용 가능한 요소라도 포커스가 이동하지 않게 된다.
div 태그에 tabindex를 0으로 주면서 keydown 이벤트가 인식되게 된다.
'웹 (web) > 프론트엔드' 카테고리의 다른 글
React로 Server Side Rendering 구현하기 - 3. Data fetching (0) | 2023.03.30 |
---|---|
React로 Server Side Rendering 구현하기 - 2. 서버 구축 (1) | 2023.03.09 |
React로 Server Side Rendering 구현하기 - 1. 웹팩 설정 (0) | 2023.03.09 |
React Query 살펴보기 (0) | 2023.01.12 |
[Next.js] Next 동작 원리를 알아보자 (0) | 2022.12.03 |