개인 작업으로 크리스마스 파티 초대장을 만들면서 눈 내리는 효과를 적용하고 싶어 시도해보았다.

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
minsun309