image

눈 내리는 효과

태그
JavascriptReact
상세설명눈 내리는 효과
작성일자2024.02.03

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

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;
    `;

    결과