requestAnimationFrame을 사용해 ProgressBar 만들기

0
post-custom-banner

⚒️ setInterval과 requestAnimationFrame

첫 번째, 두 번째 막대는 각각 setInterval과 requestAnimationFrame을 이용해서 너비값을 증가시키는 애니메이션이다.

setInterval을 이용한 막대 그래프는 requestAnimationFrame을 이용한 막대 그래프에 비해 일정한 간격으로 실행이 되지 않고 프레임이 유연하지 못한 것을 확인할 수 있다.

  • setInterval을 사용한 애니메이션
  const startInterval = () => {
    const interval = document.querySelector('#interval') as HTMLDivElement;

    const id = setInterval(() => {
      interval.style.width = `${intervalWidth}px`;
      intervalWidth += 1;

      if (intervalWidth > window.innerWidth) clearInterval(id);
    }, 1000 / 60);
  };
  • requestAnimationFrame을 사용한 애니메이션
  const startAnimationFrame = () => {
    const animationFrame = document.querySelector(
      '#animationFrame'
    ) as HTMLDivElement;

    animationFrame.style.width = `${requestWidth}px`;
    requestWidth += 1;
    const id = requestAnimationFrame(startAnimationFrame);
    if (requestWidth > window.innerWidth) cancelAnimationFrame(id);
  };

이렇게 다르게 동작하는 이유는 setInterval로 width를 자주 변경하게 되면 자바스크립트가 실행되는 동안(브라우저에서 다른 작업이 처리되는 동안) 레이아웃 변경이 지정된 간격보다 더 늦게 또는 더 빠르게 실행될 수 있다. 즉 간격이 일정하지 못하게 되고 실제 화면에 렌더링 되는 프레임의 수가 줄어드는 frame drop 현상으로 이어지게 된다. 이로인해 버벅이는 듯한 애니메이션으로 이어지게 되는 것이다.

반면 requestAnimationFrame은 브라우저의 렌더링 주기와 동기화 돼서 실행이된다. requestAnimationFrame 콜백에 DOM을 변경하는 함수를 넣어주면 해당 변경은 브라우저의 렌더링 주기에 최적화된 방식으로 반영이 되기 때문에 setInterval보다 requestAnimation을 사용할 때 더 유연한 애니메이션을 제공할 수 있게된다.



✔️ requsetAnimationFrame으로 scroll에 따른 progessBar 구현하기

비용이 많이 드는 레이아웃 변경 대신 transform의 scaleX로 progressBar의 너비값이 변하도록 적용했다.

  • 전체 코드
function ScrollProgressBar() {
    const [progess, setProgress] = useState(0);
    const rafRef = useRef<number | null>(null);
    
    useEffect(() => {
        const scroll = () => { 
            const scrollTop = document.documentElement.scrollTop
            // 문서 전체 높이 - 뷰포트 높이 값
            const height = document.documentElement.scrollHeight - document.documentElement.clientHeight

            // 중복된 작업이 반복적으로 일어나지 않도록 방지코드
            if (rafRef.current) {
                cancelAnimationFrame(rafRef.current)    
            }

            rafRef.current = requestAnimationFrame(() => {
                setProgress(scrollTop / height)
            })
        }

        window.addEventListener('scroll', scroll)
        
        return () => {
            if (rafRef.current) {
                cancelAnimationFrame(rafRef.current)    
            }

            window.removeEventListener('scroll', scroll)
        }
    },[])
    return (
        <div style={{ transform : `scaleX(${progess})`, transformOrigin : 'left', 
        backgroundColor:'', height : 8, position :'sticky', 
        top : 62,  zIndex:999, background: 'rgb(255 196 35)'}}>

        </div>
    )
}

💡

post-custom-banner

0개의 댓글