개인 작업으로 크리스마스 파티 초대장을 만들면서 눈 내리는 효과를 적용하고 싶어 시도해보았다.
Next.js에서 emotion을 활용해 눈 내리는 효과를 만들었다.
각 눈송이 사이즈, blur, 눈 내리는 속도 설정
필요한 상수
- snowSizeBase: 눈송이의 기본 크기를 나타냅니다. 모든 눈송이의 크기는 이 값을 기반으로 무작위로 조절된다.
- browserBuffer: 눈송이가 나타날 수 있는 브라우저 화면 외의 추가적인 여유 공간을 나타냅니다. 이 값은 눈송이가 화면을 벗어나지 않도록 하는 역할을 한다.
- leftPosition: 눈송이의 시작 위치의 왼쪽 한계를 나타냅니다. 화면 왼쪽 끝으로부터 이 값만큼 떨어진 위치까지 눈송이가 나타난다.
- animateSpeedBase: 눈송이가 화면을 향해 떨어지는 속도를 나타냅니다. 이 값이 클수록 눈송이는 빠르게 떨어집니다.
- minimumFallingSpeed: 최소 떨어지는 속도를 나타냅니다. animateSpeedBase에 추가되어 무작위로 결정된 값이 더해져 최종 떨어지는 속도가 결정된다.
- animateDelayBase: 각 눈송이의 애니메이션 시작 지연 시간의 기본 값을 나타냅니다. 각 눈송이는 서로 다른 시작 시간을 가지게 된다.
- blurBase: 눈송이에 적용되는 흐림 효과의 기본 값을 나타냅니다. 눈송이의 모양을 부드럽게 만든다.
- snowdropsLength: 눈송이의 총 개수를 나타냅니다. 생성할 눈송이의 총 수를 결정한다.
눈송이 하나를 나타내며, 그 눈송이의 특성을 무작위로 설정
배열의 길이가 snowdropsLength인 배열을 생성하고, 각 요소의 인덱스를 키로 하는 새로운 배열을 만든다.
공통 사항
Math.abs() 함수를 활용하여 음수를 양수로 변환한다.
Math.random() 은 0 이상 1 미만의 난수를 생성한다.
- size : 각 눈송이의 크기를 무작위로 설정합니다.
Math.random() * snowSizeBase - Math.random() * snowSizeBase 를 활용하여 무작위 크기를 정한다.
ex) snowSizeBase 가 10일 경우
Math.random() * 10 / - Math.random() * 10 을 주어 랜덤으로 4.8 / 7.2 값이 주어져 두 값을 빼면 4.8 - 7.2 가 되어 최종적으로 -2.4가 되어 음수를 양수로 만든 값을 width, height값에 적용한다.
- left : 각 눈송이의 시작 위치를 무작위로 설정한다. leftPosition은 눈송이의 시작 위치의 최대 한계를 나타내어 Math.random() * leftPosition - Math.random() * leftPosition 식을 통해 left 값에 % 로 적용한다.
- duration : 각 눈송이의 애니메이션 지속 시간을 무작위로 설정한다.
Math.random() * animateSpeedBase + minimumFallingSpeed 식을 통해 animation-duration 값에 적용한다.
- delay : 각 눈송이의 애니메이션 시작 지연 시간을 무작위로 설정한다.
Math.random() * animateDelayBase 식을 통해 animation-delay 값에 적용한다.
- blur : 각 눈송이에 적용될 흐림 효과를 무작위로 설정한다.
Math.random() * blurBase - Math.random() * blurBase 식을 통해 filter blur 값에 적용한다.
const Snowfall = () => {
const snowSizeBase = 10;
const browserBuffer = 100;
const leftPosition = 100 + browserBuffer;
const animateSpeedBase = 10000;
const minimumFallingSpeed = 5000;
const animateDelayBase = 5000;
const blurBase = 3;
const snowdropsLength = 300;
return (
<div
css={css`
overflow: hidden;
height: 100%;
width: 100%;
`}
>
{/* 눈송이를 배치하기 위한 내부 컨테이너 */}
<div
css={css`
width: 100%;
height: 100%;
position: relative;
`}
>
{/* 개별 눈송이를 생성하고 렌더링합니다. */}
{[...Array(snowdropsLength).keys()].map((i) => {
const size = Math.abs(
Math.random() * snowSizeBase - Math.random() * snowSizeBase
);
const left = Math.abs(
Math.random() * leftPosition - Math.random() * leftPosition
);
const duration = Math.abs(
Math.random() * animateSpeedBase + minimumFallingSpeed
);
const delay = Math.abs(Math.random() * animateDelayBase);
const blur = Math.abs(
Math.random() * blurBase - Math.random() * blurBase
);
return (
// 각 눈송이에 대한 동적 스타일 및 애니메이션을 적용한 개별 요소
<div
key={i}
css={css`
opacity: 0;
position: absolute;
top: 0;
border-radius: 100%;
background-color: #ffffff;
background-repeat: no-repeat;
background-size: 100% auto;
animation-name: ${snowDrop};
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-fill-mode: forwards;
left: ${left}%;
width: ${size}px;
height: ${size}px;
animation-duration: ${duration}ms;
animation-delay: ${delay}ms;
filter: blur(${blur}px);
`}
/>
);
})}
</div>
</div>
);
};
눈이 내리는 효과를 위한 keyframes를 정의
눈 내리는 애니메이션을 적용할 keyframes으로 animation-name에 적용하면된다.
const snowDrop = keyframes`
0% {
transform: translate(0, 0);
opacity: 0.5;
margin-left: 0;
}
10% {
margin-left: 15px;
}
20% {
margin-left: 20px;
}
25% {
transform: translate(0, 250px);
opacity: 0.75;
}
30% {
margin-left: 15px;
}
40% {
margin-left: 0;
}
50% {
transform: translate(0, 500px);
opacity: 1;
margin-left: -15px;
}
60% {
margin-left: -20px;
}
70% {
margin-left: -15px;
}
75% {
transform: translate(0, 750px);
opacity: 0.5;
}
80% {
margin-left: 0;
}
100% {
transform: translate(0, 1000px);
opacity: 1;
}
`;
전체코드
import { css, keyframes } from "@emotion/react";
const snowDrop = keyframes`
0% {
transform: translate(0, 0);
opacity: 0.5;
margin-left: 0;
}
10% {
margin-left: 15px;
}
20% {
margin-left: 20px;
}
25% {
transform: translate(0, 250px );
opacity: 0.75;
}
30% {
margin-left: 15px;
}
40% {
margin-left: 0;
}
50% {
transform: translate(0, 500px );
opacity: 1;
margin-left: -15px;
}
60% {
margin-left: -20px;
}
70% {
margin-left: -15px;
}
75% {
transform: translate(0, 750px);
opacity: 0.5;
}
80% {
margin-left: 0;
}
100% {
transform: translate(0, 1000px);
opacity: 1;
}
`;
export default function Snow() {
const snowSizeBase = 10;
const browserBuffer = 100;
const leftPosition = 100 + browserBuffer;
const animateSpeedBase = 10000;
const minimumFallingSpeed = 5000;
const animateDelayBase = 5000;
const blurBase = 3;
const snowdropsLength = 300;
return (
<section css={SnowfallContainer}>
<div
css={css`
overflow: hidden;
height: 100%;
width: 100%;
`}
>
<div
css={css`
width: 100%;
height: 100%;
position: relative;
`}
>
{[...Array(snowdropsLength).keys()].map((i) => {
const size = Math.abs(
Math.random() * snowSizeBase - Math.random() * snowSizeBase
);
const left = Math.abs(
Math.random() * leftPosition - Math.random() * leftPosition
);
const duration = Math.abs(
Math.random() * animateSpeedBase + minimumFallingSpeed
);
const delay = Math.abs(Math.random() * animateDelayBase);
const blur = Math.abs(
Math.random() * blurBase - Math.random() * blurBase
);
return (
<div
key={i}
css={css`
opacity: 0;
position: absolute;
top: 0;
border-radius: 100%;
background-color: #ffffff;
background-repeat: no-repeat;
background-size: 100% auto;
animation-name: ${snowDrop};
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-fill-mode: forwards;
left: ${left}%;
width: ${size}px;
height: ${size}px;
animation-duration: ${duration}ms;
animation-delay: ${delay}ms;
filter: blur(${blur}px);
`}
/>
);
})}
</div>
</div>
</section>
);
}
const SnowfallContainer = css`
width: 100vw;
height: 100vh;
`;
결과
'React' 카테고리의 다른 글
react-hook-form (useForm) (2) | 2024.08.30 |
---|---|
React Query(Tanstack Query) (0) | 2024.08.30 |
Pagination (0) | 2024.08.26 |
검색 기능 (0) | 2024.08.26 |
react-joyride (0) | 2024.08.26 |