라이브러리 없이 Count Up 기능 만들기

gigi·2022년 8월 9일
0

매개변수로

  • end(Count 종료값)
  • start(Count 시작값)
  • duration(Count 총 소요시간)

을 전달받아 CountUp 하는 Custom Hook을 만들었다. setInterval 을 이용하여 지정한 delay 마다 setCount에 값을 전달해 count를 업데이트 한다.

import React from 'react'
import useCountUp from '../../customHook/useCountUp'

function MetricItem({ end, start, duration }) {
  return <span>{useCountUp(end, start, duration)}</span>
}

export default MetricItem
import { useState, useEffect } from 'react'

function useCountUp(end, start = 0, duration = 2000) {
  const [count, setCount] = useState(start)

  useEffect(() => {
    let currentNum = start
    const delay = duration / end

    const countUp = setInterval(() => {
      currentNum++

      setCount(currentNum)

      if (currentNum === end) {
        clearInterval(countUp)
      }
    }, delay)
  }, [end, start, duration])

  return count.toFixed(0)
}

export default useCountUp

겪은 문제점

자연스럽게 카운트가 되게끔 값을 1씩 증가시킨다고 했을 때
duration = 2000ms, end = 1000의 상황에서 setInterval의 delay를 2ms로 설정해줘야하지만, 브라우저에서 setInterval의 호출 시간을 최소 4ms로 고정한다는 것이다.
즉 end 값이 크다면 목표값까지 카운트하는데 설정한 duration보다 더 많은 시간이 소요된다.
end 값이 몇만, 몇억대라면? 자연스럽게 하기 위해 1씩 count 하는것도 다시 생각해봐야 하는부분.

( duration이 2000ms일 경우 최대 500까지 2000ms내로 카운트가 가능하다는것인데 실제 100과 470의 카운트가 동일하게 끝나지 않는다. 최소시간이 4ms보다 더 크것같다 정확한 이유를 더 알아봐야할듯)

한마디로
여러개의 카운트업을 동일한 시간에 끝내는 기능을 구현한다면, 일정 값 이상일때 각 카운트 끝나는 시간이 다르다.

해결방법

  1. setInterval의 함수 호출 횟수를 maxCount 변수에 임의의 값으로 고정하고,
    delay 또한 delay = duration / maxCount로 고정한다. (maxCount = 50 으로 고정하였음)

  2. currentNum을 end / 50씩 더하면 되는데, 그러면 소수점까지 나타나기 때문에 return 값을 toFixed(0)을 이용해 소수점 이하는 생략한다.

  3. setInterval 호출마다 maxCount 값을 감소시키고 maxCount = 0 이 되는 순간 clearInterval로 countUp을 종료한다.

import { useState, useEffect } from 'react'

function useCountUp(end, start = 0, duration = 2000) {
  const [count, setCount] = useState(start)

  useEffect(() => {
    let currentNum = start
    let maxCount = 50
    let stepTime = duration / maxCount
    const countUp = setInterval(() => {
      currentNum += end / 50
      maxCount--
      setCount(currentNum)

      if (maxCount === 0) {
        clearInterval(countUp)
      }
    }, stepTime)
  }, [end, start, duration])

  return count.toFixed(0)
}

export default useCountUp


고민중인 부분 (해결)

조건

  • 각 숫자는 0부터 시작할 것
  • 세 숫자 모두 2000ms 내로 카운트를 끝낼 것
  • 카운트가 끝날때쯤 카운트 올라가는 속도를 늦추는 효과를 구현할 것

카운트가 끝나갈때 쯤 카운트가 느려지는 효과를 구현하려면 일정 값 이상에서는 카운트를 1씩 증가하게끔 구현을 해야하는데 각각 1/50 씩 더해주는 상황이기때문에 동일한 값에서 멈추려면 타이밍이 안맞는다.

목표값에서 -5까지만 1/50씩 더하고나서 1씩 더하면?
ex) duration = 2000 일때,
500ms 동안은 ((end-5) / 50)만큼씩 빠르게 카운트를하고
1500ms 동안은 나머지 5를 느리게 카운트 한다.

  • end = 700 일 경우
    10ms마다 (695 / 50) 씩 더해주고 300ms 마다 1을 더해준다.
  • end = 100 일 경우
    10ms마다 (95 / 50) 씩 더해주고 300ms 마다 1을 더해준다.
  • end = 470 일 경우
    10ms마다 (465 / 50) 씩 더해주고 300ms 마다 1을 더해준다.
import { useState, useEffect } from 'react'

function useCountUp(end, start = 0, duration = 2000) {
  const [count, setCount] = useState(start)

  useEffect(() => {
    let currentNum = start
    let maxCount1 = 50
    let maxCount2 = 5
    const delay1 = (duration / maxCount1) * (1 / 4)
    const delay2 = (duration / maxCount2) * (3 / 4)

    const countUp1 = setInterval(() => {
      currentNum += (end - 5) / 50
      maxCount1--
      setCount(currentNum)

      if (maxCount1 === 0) {
        setCount(Math.round(currentNum))
        clearInterval(countUp1)
      }
    }, delay1)

    const countUp2 = setInterval(() => {
      currentNum++
      maxCount2--
      setCount(Math.round(currentNum))

      if (maxCount2 === 0) {
        clearInterval(countUp2)
      }
    }, delay2)
  }, [end, start, duration])

  return count.toFixed(0)
}

export default useCountUp

setInterval 을 두개로 나누어 setInterval1이 종료되면 setInterval2가 실행 되게끔 하였다.

초반 카운트 시간이 너무 짧아서 생각했던 수준의 동작은 아니지만 조건을 모두 갖춘 countUp 기능을 구현했다.



참고
https://shylog.com/react-custom-hooks-scroll-animation-countup/

0개의 댓글