최근에 재미삼아 연습겸 쿠키 클리커 같은 웹 게임을 만들어 보고 있었습니다.
그런데 쿠키 클리커를 참고해서 만들다 보니 클릭하는 부분에서 초당 쿠키의 갯수에 비례해서 쿠키가 내리는 효과가 있던 것입니다.

이것을 비슷하게 구현해 보기로 마음을 먹고 어떻게 할지 고민을 하기 시작했습니다.
그때 생각난것이 이전 프로젝트에서 다른분이 구현했던 눈 내리는 효과가 생각이 났는데 거기서 눈 모양만 이미지로 바꿔서 사용을 하면 되겠다는 생각이 들게 되었습니다.
그래서 우선 눈 내리는 효과를 찾아보기로 했습니다.
우연히 알게된 코드센드박스에 다른분이 올려주신 것을 참고해 만들게 되었습니다.
코드센드박스에서 올라와 있는 코드는 우선 폴더 구조가 이러했다.

그리고 코드를 하나하나 열어보다보니 생각보다는 쉽다는 것을 알게 되었습니다.
저기 있는 코드를 보여주며 설명 해보자면..
우선 가장 중요한 snowflakes.tsx 입니다
import React, { useState, useEffect } from "react";
import "./Snowflakes.scss";
import { generateRandomNumber } from "../../utils/math";
import IconSnow from "../icons/IconSnow";
interface Snowflakes {
left: number;
fallDelay: number;
shakeDelay: number;
blur: number;
opacity: number;
size: number;
}
interface SnowflakesProps {
count?: number; // 눈송이 개수
}
export default function Snowflakes({ count = 17 }: SnowflakesProps) {
const [snowflake, setSnowflake] = useState<Snowflakes[]>([]);
// 외부에서 On/Off를 제어할 경우 사용한다.
const isShow = true;
// 클라이언트 사이드에서만 실행되도록.
useEffect(() => {
const newSnowflake = Array.from({ length: count }).map(() => {
const fallDelay = generateRandomNumber(0, 15, { fixed: 2 });
const shakeDelay = Math.min(
generateRandomNumber(0, 10, { fixed: 1 }),
Number.parseFloat((fallDelay - 0.07).toFixed(1))
); // fallDelay보다 무조건 길어야 한다. 그렇지 않으면 일부 구간 눈송이가 일자로 내리게 된다.
return {
left: generateRandomNumber(0, 100),
fallDelay,
shakeDelay,
blur: generateRandomNumber(0.2, 0.5, { fixed: 1 }),
opacity: generateRandomNumber(0.55, 0.95, { fixed: 2 }),
size: generateRandomNumber(12, 18),
};
});
setSnowflake(newSnowflake);
}, []);
return (
<div
className={`snowflake ${
isShow && snowflake.length ? "visible" : "hidden"
}`}
aria-hidden="true"
>
{snowflake.map((flake, index) => (
<div
key={`flake-${index}`}
className="snowflake"
style={{
left: `${flake.left}%`,
filter: `blur(${flake.blur}px)`,
opacity: `${flake.opacity}`,
animationDelay: `${flake.fallDelay}s, ${flake.shakeDelay}s`,
WebkitAnimationDelay: `${flake.fallDelay}s, ${flake.shakeDelay}s`,
}}
>
<IconSnow size={flake.size} />
</div>
))}
</div>
);
}
여기서 math.ts에서 계산식을 가져오는데 우선 빼고 보게되면 count 수만큼 내리게 합니다.
계산하는 부분인 useEffect 부분을 빼고 보면 더 간단하게 보일 것입니다.
정말 정해준 숫자만큼 map을 돌려서 만들어 주는 것이기 때문입니다.
그리고 나같은 경우는 쿠키가 내리게 하려고 했으니 <IconSnow /> 부분을 쿠키 이미지로 변경해주게 되었습니다.
그리고 useEffect 부분에서 계산해서 state에 넣어 두었던 값들을 활용해서 위치를 잡게 됩니다.
랜덤한 값을 받게 math.ts가 설정되어 있어 매번 다른 위치값, 블러값, 투명도등을 스타일로 받게 되는 것입니다.
참고로 저 파일의 css는
@keyframes snowflake-fall {
0% {
top: -10%;
}
100% {
top: 100%;
}
}
@keyframes snowflake-shake {
0%,
100% {
transform: translateX(0px);
}
50% {
transform: translateX(80px);
}
}
.snowflake {
position: fixed;
left: 0;
top: 0;
.snowflake {
pointer-events: none; /* 다른 클릭이나 인터랙션을 방해하지 않도록 */
color: #fff;
text-shadow: 0 0 1px #000;
position: fixed;
top: -10%;
z-index: 0;
user-select: none;
cursor: default;
animation: snowflake-fall 10s linear infinite running,
snowflake-shake 3s ease-in-out infinite running;
}
}
이런식으로 되어 있습니다.
그리고 아까 일단 넘겨두었던 math.ts는
const generateRandomInt = (min = 0, max = 0) => {
if (min > max) {
return max;
}
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const generateRandomFloat = (min = 0.0, max = 1.0, fixed = 1) => {
if (min > max) {
return max;
}
return parseFloat((Math.random() * (max - min) + min).toFixed(fixed));
};
interface RandomNumberOptionProps {
fixed?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
/**
*
* @param min 최소값 (이상)
* @param max 최대값 (이하)
* @param option fixed : 생성할 소수점 자리수
* @returns 랜덤수
*/
export const generateRandomNumber = (
min: number,
max: number,
option?: RandomNumberOptionProps
): number => {
if (min > max) {
return max;
}
if (option) {
const { fixed } = option;
if (fixed) {
return generateRandomFloat(min, max, fixed);
}
}
return generateRandomInt(min, max);
};
이런식으로 상황에 따라 랜덤한 값이 들어오도록 함수가 짜여져 있다.
처음 보면 어려워보일 수 있는데 호출해서 사용하는 곳부터 해서 따라 올라가다보면 이해하기 어렵지 않았습니다.
그리고 마지막!
가져다 사용은 어렵지 않습니다.
import "./styles.css";
import Snowflakes from "../src/components/snowflakes/Snowflakes";
export default function App() {
return (
<div className="App">
<Snowflakes />
</div>
);
}
그냥 가져다가 사용하면 됩니다.
처음 봤을때는 어떻게 구현을 해야 할지 감도 안오고 어려워 보이기만 했었는데 이렇게 다른분이 간단하게 구현을 해주신 것을 보고 따라 해보며 이해를 해보기 생각보다는 어렵지 않았던 것 같습니다. 너무 겁만 먹지말고 다음부터는 한번 해보는 것도 좋을 것 같다고 생각이 들기도 합니다.
위 코드센트박스 링크
https://codesandbox.io/p/sandbox/snow-effect-w6dqw2?file=%2Fsrc%2FApp.tsx%3A1%2C1-11%2C1
'이것저것' 카테고리의 다른 글
| Nominatim API (0) | 2024.05.09 |
|---|---|
| css를 사용하며 (풀스크린..?) (0) | 2024.04.30 |
| Mixed Content (Next.js) (0) | 2024.04.19 |
| js의 console (0) | 2024.04.14 |
| axios (0) | 2024.04.13 |