눈 내리는 효과 만들기

Reference

https://gurtn.tistory.com/195


사실 여기서 HTML이랑 CSS는 별 거 없다. 진짜로 별 거 없다.

<html>

<head>
<title>Let it snow</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<canvas width="1920" height='1080' />
</body>
<script src="script.js"></script>
</html>

이게 HTML이고

html {

margin:0;
padding:0;
}

body {
margin:0;
padding:0;
overflow:hidden;
}

이게 CSS다. 진짜로 이게 다다.

그렇다는건 뭐다? 그죠 자바스크립트가 본론입니다. 일단 차근차근 따라가보면 어렵지 않은데 코드가 길어서 아 안돼 할 수는 있음… 근데 부분부분 뜯어보면 대충 이게 뭐 하는 함수인지 이해는 갈 것이다.


const $canvas = document.querySelector("canvas");

const ctx = $canvas.getContext("2d");

const getRandomRadius = () => Math.random() * 2 + 0.5;
const getRandomspeed = () => Math.random() * 0.3 + 0.5;
const getRandomDir = () => [-1, 1][Math.floor(Math.random() * 2)];

여기서 위의 두 줄은 캔버스(HTML에 캔버스가 또 있다… 뭔지는 모름)를 가져오는거고 아래 세 줄은 내릴 눈의 크기, 속도, 방향을 결정하는 함수다.

const Snow = {

data: [],
canvasWidth: $canvas.clientWidth,
canvasHeight: $canvas.clientHeight,

init() {
Snow.make();
Snow.loop();
},

loop() {
Snow.move();
Snow.draw();

window.requestAnimationFrame(Snow.loop);
//얘를 이용해서 재귀를 돌린다
},

make() {
//눈 데이터를 만드는 함수
const data = [];

for (let i = 0; i < 250; i++) {
const x = Math.random() * Snow.canvasWidth;
const y = Math.random() * Snow.canvasHeight;

const size = getRandomRadius();
const speed = getRandomspeed();
const dir = getRandomDir();

data.push({ x, y, size, speed, dir });
}

Snow.data = data;
},
move() {
//방향에 맞게 이동
Snow.data = Snow.data.map((item) => {
item.x += item.dir * item.speed;
item.y += item.speed;

//캔버스를 벗어났나요?
const isMinOverPositionX = -item.size > item.x;
const isMaxOverPositionX = item.x > Snow.canvasWidth;
const isOverPositionY = item.y > Snow.canvasHeight;

//벗어났다면 반대방향, 맨 위로
if (isMinOverPositionX || isMaxOverPositionX) {
item.dir *= -1;
}
if (isOverPositionY) {
item.y = -item.size;
}

return item;
});
},
draw() {
//CSS에 배경색 설정이 따로 안됐던게 이것때문이었고만?
ctx.clearRect(0, 0, Snow.canvasWidth, Snow.canvasHeight);
ctx.fillStyle = '#010101';
ctx.fillRect(0, 0, Snow.canvasWidth, Snow.canvasHeight);

Snow.data.forEach((item) => {
ctx.beginPath();
ctx.fillStyle = 'rgba(255,255,255,.6)';
ctx.arc(item.x, item.y, item.size, 0, Math.PI * 2); //눈을 원형으로 만들어준다. 없으면 네모눈이 내리나?
ctx.fill();
ctx.closePath();
});
},
};

이쪽이 메인디쉬인 Snow 함수. 여기에 눈을 뿌리기 위한 모든 요소가 들어간다. 일단 다섯개의 함수로 구성되어 있는데, 위쪽부터 순서대로 1) 눈을 만드는(init) 함수(얘는 뭐 하는건 없고 init() 호출하면 make랑 loop가 세트로 소환된다), 2) 재귀 돌리는 함수, 3) 눈 데이터를 만드는 함수(저기 for문 안에 250을 바꾸면 눈 갯수가 바뀐다), 4) 눈이 제대로 이동하게 해 주는 함수, 5) 배경 채우고 눈을 원형으로 만들어주는 함수 이렇게 다섯개가 있다.

보면 위에 두개는 단촐한데 밑에 세개는 어우… 뭐가 많다 그죠? 일단 make는 말 그대로 눈 데이터를 만드는 함수이다. 저기 for문에 있는 250을 다른 숫자로 바꾸면 눈 개수가 변화하는데 한 1000정도 넣어두면 저기 어디 시베리아 평원에서 흩날리는 눈폭풍을 볼 수 있다. 눈의 크기, 방향, 속도는 위에 짜 둔 함수가 랜덤으로 정해주는데 이 함수의 수치를 조절하면 눈의 크기, 속도, 방향도 제어가 가능하다.

move는 눈이 제대로 이동하게 해 주는 함수인데, 이게 뭔 소리냐면 눈이 내리다보면 화면 밖으로 나가게 된다. 그러면 그대로 빠이짜이찌엔 안녕히계세요 여러분 하는 게 아니라 방향을 잡아주는 역할을 하는 함수이다. 나도 처음에 보고 뭔소린가 했는데 코딩하면서 설명 보고 납득함.

draw에서 볼 것은 fillstyle와 arc이다. fillstyle은 배경색을 채워주는 것이고, 그래서 어두운 배경에 눈이 흩날리고 있음에도 CSS에는 아무것도 없이 오버플로우 숨기라는 것만 되어 있었다. arc은… 이게 있어야 눈이 둥글어진다.

window.onresize = () => {

Snow.canvasWidth = $canvas.clientWidth;
Snow.canvasHeight = $canvas.clientHeight;

Snow.make();
};

이쪽은 창 크기가 바뀌면 그 크기에 맞춰서 눈 내리는 저기를 다시 정해주는 함수인 듯… 나도 모르것다.