React Custom Hooks로 scroll animation 만들기 CountUp편

React Custom Hooks로 scroll animation 만들기 CountUp편

React Custom Hooks로 scroll animation 만들기 CountUp편

해당 글은 김주성님의 세미나를 듣고 작성한 글 입니다.

보통 금액이나, 사용자 수 등 숫자와 관련된 내용에 Animation을 추가한다 하면 Count Animation을 많이 사용합니다. React Custom Hooks을 사용하면 Scroll에 반응하는 CountUp Animation을 재사용 가능하게 만들 수 있습니다.

유튜브
스크롤 타이밍에 따라 올라가는 숫자들 (gif라 끊기네요 ㅠ)

useScrollCount 구현하기

스크롤 타이밍에 따라 숫자가 CountUp 되는 animation은 크게 3가지 부분으로 나눌 수 있습니다.

  1. CountUp Animation을 구현한다.
  2. 특정 스크롤 시점에 Animation을 실행 시키도록 트리거 이벤트를 만든다.
  3. Animation 트리거 이벤트를 DOM에 지정한다.

이전에 작성했던 React Custom Hooks로 scroll animation 만들기 FadeIn 포스트와 구현 방식이 동일하며, FadeIn Animation 대신 CountUp Animation 로직을 추가한다는 것만 다릅니다.

Hook 생성 후 Scroll 트리거 이벤트 만들기

useScrollHook.js 라는 파일을 생성 후 다음과 같이 작성합니다.

const useScrollCount = () => {
  const dom = useRef();

  const handleScroll = useCallback(([entry]) => {
    const { current } = dom;

    if(entry.isIntersecting) {
      // 원하는 이벤트를 추가 할 것
    }
  }, []);

  useEffect(() => {
    let observer;
    const { current } = dom;

    if (current) {
      observer = new IntersectionObserver(handleScroll, { threshold: 0.7 });
      observer.observe(current);

      return () => observer && observer.disconnect();
    };
  }, [handleScroll]);

  return {
    ref: dom,
  };
}

export default useScrollCount;

위의 코드는 IntersectionObserver를 사용하여 지정된 element가 70%정도 노출 되었을 때 ref를 사용해 지정한 DOM에 이벤트를 발생 시키는 custom 훅 입니다.

구현 과정과 세부 내용들은 React Custom Hooks로 scroll animation 만들기 FadeIn 포스트‘custom hook 생성하기’‘Scroll 트리거 이벤트 만들기’를 봐주시면 감사하겠습니다.

이렇게 만들어진 Hook은 아래 코드처럼 사용 할 수 있습니다.

import React from 'react';
import { useScrollCount } from '@/hooks';

const Figure = () => {
  const animatedItem = useScrollCount();
  
  return (
  	<Wrapper>
    	<Title>Total Projects</Title>
      <Description>
      	Consequat interdum varius sit amet mattis vulputate enim.
      </Description>
      <p {...animatedItem} />
    </Wrapper>
  )
}

Count Animation 구현하기

Count Animation을 가장 쉽게 구현하는 방법은 setInterval을 사용하는 것 입니다.

setInterval()일정 시간마다 반복 실행하는 함수 인데요.

뱅크샐러드 채용

위와 같이 Slide 형태의 Image 목록을 만들고 정해진 시간 간격으로 움직이게 하거나, 일정 주기로 계속해서 서버와 통신이 필요한 경우 등과 같이 활용 할 수 있습니다.

setInterval() 함수의 기본적인 사용 방법은 아래와 같습니다.

const interval = setInterval("실행할 함수", "시간(ms)");  // 생성 시점부터 동작

clearInterval(interval) // 반복 동작을 취소하고 싶을 때

별도로 변수 선언 없이도 setInterval 함수 사용이 가능하지만, 변수에 따로 저장하면 추후 clearInterval 함수를 통해 원하는 타이밍에 취소 할 수 있습니다.

해당 함수를 이용하여 초기 값(start) 부터 목표 값(end), 원하는 애니메이션 시간(duration)을 받아와 CountUp을 구현 할 수 있습니다.

const stepTime = Math.abs(Math.floor(duration / (end - start))); // 1

if (entry.isIntersecting) {
  let currentNumber = start;
  const counter = setInterval(() => {
    currentNumber += 1;
    
    if (currentNumber === end) { // 2
      clearInterval(counter);
    }
  }, stepTime); // 3
}

1 의 stepTime은 setInterval의 반복 시간을 설정하는데 사용됩니다. N초 마다 숫자가 1씩 올라가야 목표 값에 도달 할 지 계산하여 해당 시간마다 1씩 올라가도록 3번에 넣어줍니다.

2의 경우 목표치에 도달 했을 때 clearInterval을 사용하여 해당 이벤트를 중단 시키는데 사용됩니다.

마무리

최종 코드는 아래와 같습니다.

const useScrollCount = (end, start = 0, duration = 3000) => {
  const dom = useRef();
  const stepTime = Math.abs(Math.floor(duration / (end - start))); // 1
  
  const handleScroll = useCallback(([entry]) => {
    const { current } = dom;
    
    if(entry.isIntersecting) {
      let currentNumber = start;
      const counter = setInterval(() => {
        currentNumber += 1;
    
        if (currentNumber === end) {
          clearInterval(counter);
        }
      }, stepTime)
    }
  }, []);

  useEffect(() => {
    let observer;
    const { current } = dom;

    if (current) {
      observer = new IntersectionObserver(handleScroll, { threshold: 0.7 });
      observer.observe(current);

      return () => observer && observer.disconnect();
    };
  }, [handleScroll]);

  return {
    ref: dom,
  };
}

export default useScrollCount;

참고자료

🌝동글동글 🌚