ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React로 Scroll Reveal Animation 구현하고 컴포넌트화 하기
    React 2024. 4. 30. 14:36

    샤랄라 애니메이션

     

     

    저번 게시물에서 Scroll Reveal Animation을 React/Next.js로 구현하기 앞서 동작 원리를 알아보기 위해 JavaScript로 구현해 보았습니다. 이번에는 실제 프로젝트에 적용하기 위해 React로 구현하고 컴포넌트화 했습니다! 


     

    JavaScript로 Scroll Reveal Animation 구현하기

    Next.js 로 프로젝트 중 토스 홈페이지를 보고 스크롤 이벤트 시 요소들이 여러 방향으로 나타나는 애니메이션을 프로젝트 소개 페이지(Intro Page)에 적용하고 싶었습니다. 그래서 React로 만들기에

    dev-ea-jung.tistory.com

     

     

     

    ✅  구현하고 싶은 내용

    props로 animation direction을 받을 수 있는 <ScrollRevealAnimationWrapper /> 컴포넌트를 만들려고 합니다.

    애니메이션을 적용하고 싶은 요소가 있으면 감싸주고 animation direction 전달해주면 원하는 방향으로 이동하는 애니메이션이 적용됩니다. 

     

    ✅  구현 과정

    우선 저번 게시물에서 만들었던 뷰포트 안에 요소가 들어왔는지 검사하는 함수를 checkIsInViewport() 훅으로, 스크롤 이벤트를 감지하는 함수를 useWindowScrollEvent() 훅으로 뺐습니다. 

     

     

    export const checkIsInViewport = (elem: DOMRect | null): boolean => {
      if (!elem || !window) {
        return false
      }
    
      const elementTop = elem.top
      const elementBottom = elem.bottom
    
      return elementBottom > 0 && elementTop <= window.innerHeight
    }

     

    import { useEffect } from 'react'
    
    export const useWindowScrollEvent = (listener: () => void): void => {
      useEffect(() => {
        window.addEventListener('scroll', listener)
    
        return () => {
          window.removeEventListener('scroll', listener)
        }
      }, [])
    }

     

    저는 <ScrollRevealAnimationWrapper />을 CSS module 방법을 이용하여 만들어 주었어요. 

     

    작동방식은 다음과 같습니다.

     

    1. useWindowScrollEvent() 훅이 스크롤 이벤트를 감지하여 이벤트 리스너를 작동시킵니다. 
    2. 스크롤 이벤트가 동작하는 동안 애니메이션 이벤트를 적용하고 싶은 요소가 뷰포트에 보이면 이를 checkIsInViewport()가 감지합니다. 
    3. 원하는 요소가 뷰포트 상으로 들어오면 checkIsInViewport()는 true를 반환하고, animation staete 또한 true로 전환됩니다. 
    4. animation가 true가 되면(&&) style[animationDirection + 'Animation'] 스타일이 적용되면서 애니메이션 스타일이 추가됩니다. 
    5. 선택한 animation direction(top, bottom, right, left)에 맞게 해당 요소에 애니메이션이 적용됩니다.  

     

    // ScrollRevealAnimationWrapper.tsx
    
    import React, { useRef, useState } from 'react'
    import { checkIsInViewport } from '../_hooks/checkIsInViewport'
    import { useWindowScrollEvent } from '../_hooks/useWindowScrollEvent'
    import style from './ScrollRevealAnimWrapper.module.css'
    
    type props = {
      children: React.ReactNode
      animationDirection: 'top' | 'bottom' | 'right' | 'left'
    }
    
    const ScrollRevealAnimationWrapper: React.FC<props> = ({
      animationDirection,
      children,
    }) => {
      const [animation, setAnimation] = useState(true)
      const areaRef = useRef<HTMLDivElement>(null)
    
      const handleScrollAnimation = () => {
        const element = areaRef.current?.getBoundingClientRect()
    
        if (element) {
          console.log(checkIsInViewport(element))
    
          setAnimation(checkIsInViewport(element))
        }
      }
    
      useWindowScrollEvent(handleScrollAnimation)
    
      return (
        <div
          className={`${style.container} ${
            animation && style[animationDirection + 'Animation']
          }`}
        >
          <div ref={areaRef}>{children}</div>
        </div>
      )
    }
    
    export default ScrollRevealAnimationWrapper

     

     

    // ScrollRevealAnimWrapper.module.css
    
    .container {
      opacity: 0;
    }
    
    .topAnimation {
      animation: top 2s both;
    }
    
    .bottomAnimation {
      animation: bottom 2s both;
    }
    
    .rightAnimation {
      animation: right 2s both;
    }
    
    .leftAnimation {
      animation: left 2s both;
    }
    
    @keyframes top {
      from {
        transform: translateY(5rem);
        opacity: 0;
      }
      to {
        transform: translateY(0);
        opacity: 1;
      }
    }
    
    @keyframes bottom {
      from {
        transform: translateY(-5rem);
        opacity: 0;
      }
      to {
        transform: translateY(0);
        opacity: 1;
      }
    }
    
    @keyframes right {
      from {
        transform: translateX(-5rem);
        opacity: 0;
      }
      to {
        transform: translateX(0);
        opacity: 1;
      }
    }
    
    @keyframes left {
      from {
        transform: translateX(5rem);
        opacity: 0;
      }
      to {
        transform: translateX(0);
        opacity: 1;
      }
    }

     

     

    ✅  결과물

     

     

     

    ✅  추가 할 만한 내용 🧐

    우선 기능 구현을 중심으로 코드를 짰지만 콘솔 탭을 보면 스크롤 이벤트에 대한 이벤트 리스너가 불필요하게 많이 호출되는 것을 알 수 있었습니다. 

     

     

     

     

    그래서 쓰로틀링을 적용해서 최적화해 보면 좋을 것 같다고 생각했습니다! 

    쓰로틀링 : 짧은 시간 동안 연속해서 발생한 이벤트들을 일정 시간 단위(delay)로 그룹화하여, 처음 또는 마지막 이벤트 핸들러만 호출하도록 하는 것
    반응형

    댓글

Designed by Tistory.