
<canvas> HTML 태그 자체이다.
이 입자만의 고유한 위치와 속도를 저장하고, ctx에 대한 연결 정보를 확보하여,언제든 독립적으로 작동할 수 있도록 객체를 준비하는 역할을 한다.
외부로부터 값 받기
=> constructor의 8개 인자는 init() 함수가 new Particle(...)을 호출할 때 넘겨주는 값들이다. 이 값들의 타입을 선언한다.
객체 내부에 값 저장하기
=> this.ctx = ctx;
=> 여기서 this는 지금 막 생성되는 개별적인 Particle 객체 자신을 의미한다.
=> 오른쪽에 있는 인자 ctx의 값을, 왼쪽에 있는 this.ctx라는 객체의 고유한 속성에 저장한다.

this.ctx.beginPath();
=> 새로운 그림을 그릴 준비를 시작하라는 명령어
this.ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
=> arc(): Canvas API에서 호(Arc) 또는 원(Circle)을 그리는 함수
=> this.x, this.y: 입자의 현재 위치를 원의 중심 좌표로 지정한다.
=> this.size: 입자의 반지름 크기를 지정한다.
=> 0, Math.PI * 2: 호를 그리기 시작하는 각도(0 라디안)부터 끝나는 각도(2π 라디안 = 360도)까지 지정한다.
=> false: 원을 그리는 방향을 false(시계방향)으로 설정한다.
=> 즉, 이 코드는 완전한 원을 그린다.
this.ctx.fillStyle = this.color;
=> 채우기 색상을 설정하는 속성
=> 원의 내부를 this.color에 해당하는 색으로 채울 준비를 한다.
this.ctx.fill();
=> 설정된 색상(fillStyle)으로 방금 스케치한 원의 내부를 채운다.

X축 경계 처리
if (this.x > this.canvas.width || this.x < 0)
this.directionX = -this.directionX;
Y축 경계 처리
if (this.y > this.canvas.height || this.y < 0)
this.directionY = -this.directionY;
현재 위치 업데이트 (실제 이동)
this.x += this.directionX;
this.y += this.directionY;
this.draw();

useRef()
=> React에게 "리렌더링이 일어나도 내용물을 잊지 않는 참조 상자를 하나 만들어줘"라고 명령하는 훅이다.
<HTMLCanvasElement>
<...> (제네릭 문법): 이 참조 상자가 담을 값의 종류를 TypeScript에 알려준다.
HTMLCanvasElement: JavaScript/DOM에서 <canvas> 태그를 가리킬 때 사용하는 공식 타입 이름이다.
=> 이 canvasRef 상자에는 오직 <canvas> HTML 태그만 담을 수 있다고 미리 선언한 것이다.
(null): useRef로 만든 상자의 초기 값.


React 컴포넌트 안에서 Canvas API를 안전하게 사용하기 위한 필수적인 안전장치이다.
const canvas = canvasRef.current;
=> useRef 훅으로 만든 canvasRef의 .current를 열어서, 그 안에 담긴 실제 <canvas> HTML 태그를 canvas 변수에 저장한다.
if (!canvas) return;
=> 만약 canvas 변수가 null이라면 (비어있다면), 이 useEffect 함수의 실행을 여기서 즉시 중단하도록 한다.
=> canvas 태그가 확실히 존재할 때만 다음 단계로 넘어가도록 보장하는 가장 기본적인 안전장치이다.
const ctx = canvas.getContext("2d"); (그리기 도구 생성)
=> canvas 태그가 존재하는 것을 확인했으므로, 이제 그 canvas 위에 2차원(2D) 그림을 그릴 수 있는 도구를 ctx 변수에 저장한다.
=> ctx: 이 변수는 Canvas API의 모든 그리기 명령어를 담고 있는 핵심 도구이다.
if (!ctx) return;
=> 만약 ctx가 null이라면, 이 useEffect 함수의 실행을 여기서 즉시 중단해라.
=> ctx가 유효한 상태일 때만 다음 단계로 넘어가도록 보장하는 안전 장치이다.
=> 즉, Canvas 태그를 찾아서, ctx를 확보하고 이 둘이 확실하게 존재할 때만 다음 코드를 실행하라는 코드

이 함수는 캔버스의 크기를 브라우저 창의 크기에 항상 맞추는 역할을 한다.
canvas.width = window.innerWidth;
=> canvas.width: <canvas> HTML 태그 자체가 가지는 가로 길이 속성이다. 이 속성에 새로운 값을 할당하면 캔버스의 실제 그리기 영역이 변경된다.
=> window.innerWidth: 사용자가 보고 있는 브라우저 창의 현재 가로 너비와 똑같이 설정한다.
canvas.height = window.innerHeight;
=> canvas.height: <canvas> HTML 태그 자체가 가지는 세로 길이 속성이다.
=> window.innerHeight: 사용자가 보고 있는 브라우저 창의 현재 세로 높이와 똑같이 설정한다.
init() 함수가 실행될 때 이 함수가 호출되어, 페이지가 로드되자마자 캔버스가 화면 전체를 덮도록 만든다.
window.addEventListener('resize', handleResize); 코드를 통해 사용자가 브라우저 창 크기를 바꿀 때마다 이 함수가 다시 호출되어 캔버스 크기를 실시간으로 조정하도록 한다. (반응형 동작)

let particlesArray: Particle[]: particlesArray 변수의 타입은 Particle이라는 선언이다.
=> Particle: Particle 클래스로 정의한 입자 객체 타입.
=> [] (대괄호): 이 타입은 Particle 객체가 하나가 아니라 여러 개 들어있는 배열이어야 한다는 뜻이다.

캔버스 크기 조정
배열 초기화 및 개수 계산
particlesArray = []: 이전에 있던 입자들을 모두 버리고 빈 배열로 초기화한다.
let numberOfParticles = (canvas.height * canvas.width) / 11000;
=> 화면 면적을 11000으로 나누어, 파티클의 총 개수를 계산한다.
=> 면적을 11000이라는 상수로 나누는 것은, 화면 11000픽셀당 파티클을 1개씩 만들겠다는 의미이다.
=> 숫자 ↑ : 파티클의 총 개수 ↓
무작위 속성 생성 (루프 내부)
* 2) + 1을 통해 1부터 3 사이의 무작위 크기를 할당합니다.* 2: 무작위 숫자의 범위가 0에서 2 미만이 된다.* ((window.innerWidth - size * 2) - (size * 2)) + size * 2)* 2): size * 2: 입자의 지름, innerWidth - 지름: 캔버스의 오른쪽 끝에서 지름만큼 안쪽으로 들어온 지점* 2) - size * 2 ): 계산한 허용 너비에서, 입자가 왼쪽 끝에서 시작할 때 필요한 size * 2 (지름)만큼의 공간을 다시 빼준다.* 2: 왼쪽 끝 경계에서 시작하지 않고, 입자가 잘리지 않는 '최소 시작 지점'을 더해 기준점을 오른쪽으로 옮긴다. (2 ~ 계산한 이동 가능한 범위+2)Particle 객체 생성 및 저장

const connect = () => {
let opacityValue = 1;
// 1. 첫 번째 파티클 A를 선택 (particlesArray 배열을 순회)
for (let a = 0; a < particlesArray.length; a++) {
// 2. 두 번째 파티클 B를 선택 (A 이후의 파티클만 비교하여 중복 방지)
for (let b = a; b < particlesArray.length; b++) {
// 3. 거리 제곱값 계산 (피타고라스 정리)
let distance =
(particlesArray[a].x - particlesArray[b].x) * (particlesArray[a].x - particlesArray[b].x) +
(particlesArray[a].y - particlesArray[b].y) * (particlesArray[a].y - particlesArray[b].y);
// 4. 연결 조건 확인 (특정 거리 이하인지)
if (distance < (canvas.width / 7) * (canvas.height / 7)) {
// 5. 선의 투명도(Opacity) 계산 (거리가 가까울수록 선명하게)
opacityValue = 1 - distance / 20000;
// 6. 선의 스타일 설정
ctx.strokeStyle = "rgba(199, 210, 254," + opacityValue + ")"; // 색상과 계산된 투명도 적용
ctx.lineWidth = 1;
// 7. 선 그리기 (Canvas API)
ctx.beginPath(); // 새로운 경로 시작
ctx.moveTo(particlesArray[a].x, particlesArray[a].y); // 파티클 A로 이동
ctx.lineTo(particlesArray[b].x, particlesArray[b].y); // 파티클 B까지 선을 그림
ctx.stroke(); // 선을 실제로 화면에 그림
}
}
}
};
중첩된 for 루프
거리 계산
연결 조건
투명도 계산
입자가 매우 가까울 때 (붙어있을 때)
입자가 멀리 있을 때
입자가 매우 멀리 있을 때
선 그리기

animationFrameId = requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
particlesArray.forEach((particle) => particle.update());
connect();

const handleResize = () => { init(); };
window.addEventListener("resize", handleResize);

return () => { window.removeEventListener("resize", handleResize); };

최종 작동 흐름 (정리)
init() 실행
=> ex) x=100, directionX=0.1로 파티클을 만든다.
animate() 루프 1회차
=> 캔버스를 지운다.
=> particle.update() 호출. -> this.x가 100에서 100.1로 변경
=> this.draw() 호출. -> 위치 (100.1, y)에 그림을 그린다.
animate() 루프 2회차:
=> 캔버스를 지웁니다.
=> particle.update() 호출. -> this.x가 100.1에서 100.2로 변경
=> this.draw() 호출. -> 위치 (100.2, y)에 그림을 그린다.
