매개변수로
을 전달받아 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보다 더 크것같다 정확한 이유를 더 알아봐야할듯)
한마디로
여러개의 카운트업을 동일한 시간에 끝내는 기능을 구현한다면, 일정 값 이상일때 각 카운트 끝나는 시간이 다르다.
setInterval의 함수 호출 횟수를 maxCount 변수에 임의의 값으로 고정하고,
delay 또한 delay = duration / maxCount로 고정한다. (maxCount = 50 으로 고정하였음)
currentNum을 end / 50씩 더하면 되는데, 그러면 소수점까지 나타나기 때문에 return 값을 toFixed(0)을 이용해 소수점 이하는 생략한다.
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/