개인 작업으로 크리스마스 파티 초대장을 만들면서 눈 내리는 효과를 적용하고 싶어 시도해보았다.
Next.js에서 emotion을 활용해 눈 내리는 효과를 만들었다.
각 눈송이 사이즈, blur, 눈 내리는 속도 설정
필요한 상수
눈송이 하나를 나타내며, 그 눈송이의 특성을 무작위로 설정
배열의 길이가 snowdropsLength인 배열을 생성하고, 각 요소의 인덱스를 키로 하는 새로운 배열을 만든다.
공통 사항
Math.abs() 함수를 활용하여 음수를 양수로 변환한다.
Math.random() 은 0 이상 1 미만의 난수를 생성한다.
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값에 적용한다.
Math.random() * animateSpeedBase + minimumFallingSpeed 식을 통해 animation-duration 값에 적용한다.
Math.random() * animateDelayBase 식을 통해 animation-delay 값에 적용한다.
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; `;
결과