์๋ฐ์คํฌ๋ฆฝํธ ์ ๋๋ฉ์ด์ ์ ๊ตฌํํ ๋ requestAnimationFrame์ ์ฌ์ฉํด์ผ ํ๋ ์ด์
JavaScript Animation์ ๊ตฌํํ ๋, ๋๋ ๋ชจ๋ฅด๊ฒ setTimeout์ด๋ setInterval๋ก ์ฌ์ฉํ๋ ์ฌ๋์ด ์ฐธ๊ณ ํ ์ ์๋ ๊ธ
JS๋ก ์ ๋๋ฉ์ด์ ๊ตฌํ ์ ์ด์ ์๋ setInterval๋ก ๊ตฌํํ๋ค. ํ์ง๋ง setInterval๋ณด๋ค requestAnimationFrame์ ์ฌ์ฉํ๋๊ฒ ๋ ์ ์ ํ๋ค๋ ๊ฒ์ ์์๋ค.
์ต์ ํ๋ ๋ชจ๋ฌ์ ๊ตฌํํ๋ ค Toss์ useOverlay ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ตฌํ๋ถ๋ฅผ ์ด๋ณด๋ค๊ฐ, requestAnimationFrame
์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ ๋๋ฉ์ด์
์ด ๋๋ฝ๋ ์ ์๋ค๊ณ ์จ์๋ ๊ฒ ์๋๊ฐ.
ํ ์ค์ OverlayController.tsx ์ค ์ผ๋ถ
useEffect(() => {
// NOTE: requestAnimationFrame์ด ์์ผ๋ฉด ๊ฐ๋ Open ์ ๋๋ฉ์ด์
์ด ์คํ๋์ง ์๋๋ค.
requestAnimationFrame(() => {
setIsOpenOverlay(true);
});
}, []);
๊ทธ ์ ์ ๋ช ๋ฒ ์ด ํจ์๋ฅผ ๋ณธ ์ ์ด ์์ด์, ํด๋น ํจ์์ ๋ํด ๋ณธ๊ฒฉ์ ์ผ๋ก ์์๋ด์ผ๊ฒ ๋ค๋ ์๊ฐ์ ํ๋ค.
window.requestAnimationFrame()
ย ๋ฉ์๋๋ ๋ธ๋ผ์ฐ์ ์๊ฒ ์ํํ๊ธฐ๋ฅผ ์ํ๋ ์ ๋๋ฉ์ด์
์ ์๋ฆฌ๊ณ ๋ค์ ๋ฆฌํ์ธํธ ๋ฐ๋ก ์ ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๋๋ฉ์ด์
์ ์
๋ฐ์ดํธํ ์ง์ ๋ ํจ์๋ฅผ ํธ์ถํ๋๋ก ์์ฒญํ๋ค. ์ด ๋ฉ์๋๋ ๋ฆฌํ์ธํธ ์ด์ ์ ํธ์ถํ ์ธ์๋ก ์ฝ๋ฐฑ์ ๋ฐ๋๋ค.
๋ณดํต ์น ํ์ด์ง์ ์ ๋๋ฉ์ด์
์ ๊ตฌํํ ๋ ๊ฐ๋จํ ๊ฒ์ CSS์ ์์ฑ์ ํตํด ๊ตฌํํ๊ณค ํ์ง๋ง, ๋น๊ต์ ๋ณต์กํ (๋ธ๋ผ์ฐ์ ์ ๋์ด์ ๋ฐ๋ฅธ ๋ณํ๋ผ๋๊ฐ,,) ์ ๋๋ฉ์ด์
์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๊ตฌํํ๊ธฐ๋ ํ๋ค. ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ ๋๋ฉ์ด์
์ ๊ตฌํํ ๋ ํ๋ ์์ ๋๋ฝ์ ์ต์ํํด ์์ฐ์ค๋ฌ์ด ์ ๋๋ฉ์ด์
์ฒ๋ฆฌ์ ์ต์ ํ๋ฅผ ํ ์ ์๋๋ก ๋์์ฃผ๋ ๊ฒ์ด requestAnimationFrame()
์ด๋ค.
ํ๋ ์๊ณผ ์ฃผ์ฌ์จ์ ๋ํด์ ๋จผ์ ์์๋ณด์. ์ฃผ์ฌ์จ์ด๋ผ๋ ๊ฒ์, 1์ด ๋์ ๋ชจ๋ํฐ์ ํ๋ฉด ์ถ๋ ฅ ๋น๋๋ฅผ ๋ํ๋ด๋ ๊ฒ์ด๋ค. ์ํ๋ฅผ ๋ณผ ๋, ํ๋ฉด์ด ๋ถ๋๋ฝ๊ฒ ์์ง์ด๋ ๊ฒ ์ฒ๋ผ ๋ณด์ด์ง๋ง ์ฌ์ค ์งง์ ์๊ฐ ๊ฐ๊ฒฉ ์์ ์ด์ด์ง๋ ์ฌ์ง๋ค์ ์ฐ์ํด์ ๋ณด๋ ๊ฒ์ด๋ค.
์ธ๊ฐ์ 1์ด์ 60๋ฒ ์ด์์ ์ฌ์ง๋ค์ด ์ฐ์์ ์ผ๋ก ๋ณด์ฌ์ผ ์์ฐ์ค๋ฌ์ด ์์์ด๋ผ๊ณ ๋๋๋ค. ์ฆ 60FPS์ด ์์ด ๋์ด์ผ ์์ฐ์ค๋ฝ๋ค๊ณ ์ฌ๊ธด๋ค
๋ฐ๋ผ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ฌ์ฉ์์๊ฒ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์ ์ ๊ตฌํํ๋ ค๋ฉด, 1000ms / 60fps = 16.6ms ๋ง๋ค ์ฝ๋๋ฅผ ํธ์ถํด์ผ ํ๋ค.
์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ผ์ ์๊ฐ๋ง๋ค ์ฝ๋๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถํ๋ ๋ฐฉ๋ฒ์๋ setTimeout๊ณผ setInterval์ด ์๋ค.
์ ๋๋ฉ์ด์
์ setInterval
ํจ์๋ก ๋ง๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์๋ค.
const performAnimation = () => {
setInterval(performAnimation, 1000 / 60)
}
๊ทธ๋ฐ๋ฐ ์ requestAnimationFrame
์ ์ฌ์ฉํ๋ผ๋ ๊ฒ์ผ๊น?
ํด๋น ๋ฐฉ์์ ๋ฌธ์ ์ ์, ์ฐ๋ฆฌ๊ฐ ์ ํํ ์๊ฐ์ ์ ํํ๊ฒ ํจ์ ์ ์คํ์์ผฐ๋ค๊ณ ํ๋๋ผ๋, ๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ ์์ ๋๋ฌธ์ ๋ฐ์๊ฑฐ๋ setTimeout ํจ์๊ฐ ์ ํํ๊ฒ repaint ์์ ์ ์คํ๋์ง ์์ผ๋ฉด ๋ค์ ์ฌ์ดํด๋ก ๋ฐ๋ฆฌ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ค์ ์ฌ์ดํด(๋ค์ ๋ฆฌํ์ธํธ ์์ )๋ก ๋ฐ๋ฆฐ๋ค๋ ๊ฒ์ ์ฆ, ํ ํ๋ ์์ด ๋๋ฝ๋๋ค๋ ๊ฒ์ด๋ค.
์๋๋ setTimeout, setInterval์ ํจ์๊ฐ ํธ์ถ๋ ๋ ์ผ์ด๋๋ ์ผ์ ๋ณด์ฌ์ค๋ค.
paint - ์ด๋ก render - ๋ณด๋ผ javascript - ๋ ธ๋
16.6ms์ ํ์ด๋ฐ๊ณผ ๊ด๊ณ ์์ด JS์ฝ๋๊ฐ ์คํ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๋ฐ๋ผ์ ์ฃผ์ฌ์จ๊ณผ ๊ด๊ณ ์์ด ์คํ๋์ง ์๋ ๋๋ ์๊ณ , JS์ฝ๋๊ฐ ๋ ์ด์์ - ํ์ธํธ ๊ณผ์ ์ ์ ๋๋์ง ์์ผ๋ฉด ๋ ์ด์์- ํ์ธํธ ๊ณผ์ ๋ ๋ฐ๋ฆฌ๊ฒ ๋๊ธฐ๋ ํ๋ค.
๋ํ ํธ์ถ์ด ๋ธ๋ผ์ฐ์ ์ ์ฃผ์ฌ์จ ๋ณด๋ค ๋ง์ด ๋ ๋์๋ ์ ๋๋ฉ์ด์ ์ด ๋๊ธฐ๋ ๋๋์ ์ค๋ค.
๊ฐ๋จํ ์๊ฐํ๋ฉด 4๊ฐ์ ์ฌ์ง์ด ํธ์ถ๋๋๋ฐ ๋ง์ง๋ง ์ฌ์ง๋ง ๋ด๊ธฐ๊ฒ ๋๊ณ , ๋ฐ๋ผ์ 3๊ฐ์ ํ๋ ์์ ๋๋ฝ๋๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
requestAnimationFrame
์ ์ ๋๋ฉ์ด์
์ฝ๋๊ฐ rendering๊ณผ painting ์ด๋ฒคํธ ์ ์ ์คํ๋๋ค. ์ค์ ํ๋ฉด์ด ๊ฐฑ์ ๋์ด์ ํ์๋๋ ์ฃผ๊ธฐ์ ๋ฐ๋ผ ํจ์๋ฅผ ํธ์ถํ๊ธฐ ๋๋ฌธ์, ์์ธก ๊ฐ๋ฅํ๊ณ ๋๋ฝ์ด ์ต์ํ๋๋ค.
๋ํ SetTimeout๊ณผ SetInterval์ ์ฌ์ฉํ ๋์๋ Task Queue์ ์ ์ฅ๋๋๋ฐ, AnimationRequestFrame์ ์ฌ์ฉํ๋ฉด ๋ณ๋์ Animation Queue์ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ Promise์ด์ธ์ ๋ค๋ฅธ ๋น๋๊ธฐ ์์ (Micro task Queue)์ ๋นํด ๋ฐ๋ฆด ์ํ์ด ์ ๋ค.
๋ธ๋ผ์ฐ์ ์ ์ฐฝ์ ์จ๊ธฐ๊ฑฐ๋, ๋ค๋ฅธ ์ฐฝ์ ๋ณผ ๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ถํ์ํ ์ ๋ ฅ์ ์๋ชจํ์ง ์๋๋ก ๋ธ๋ผ์ฐ์ ์ ์ํด์ ์ผ์์ค์ง๋๋ค.
ํธ์ถ ์๊ฐ์ ์๋์ผ๋ก ์ ํด์ค์ผ ํ๋ setTimeout๊ณผ setInterval๊ณผ ๋ฌ๋ฆฌ, ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ ๋์คํ๋ ์ด์ ์ฃผ์ฌ์จ์ ๋ง์ถฐ ์ ๋๋ฉ์ด์ ์ ์๋์ผ๋ก ์คํ์ํจ๋ค. ์ด๋ ์ฃผ์ํด์ผ ํ ์ ์, 60fps์ธ ๋์คํ๋ ์ด ๋ณด๋ค 120fps์ ์ฃผ์ฌ์จ์ ๊ฐ์ง ๋์คํ๋ ์ด์์ ์ ๋๋ฉ์ด์ ์ด ๋น ๋ฅด๊ฒ ๋์ํ ์ ์๋ค๋ ์ ์ด๋ค.
๋ฐ๋ผ์ ํญ์ ํ๋ ์์์ ์ผ๋ง๋ ๋ง์ ์ ๋๋ฉ์ด์ ์ด ์งํ๋ ๊ฒ์ธ์ง ๊ณ์ฐํ๊ธฐ ์ํด ์ฒซ ๋ฒ์งธ ์ธ์(ํน์ ํ์ฌ ์๊ฐ์ ๊ฐ์ง ์ ์๋ ๋ช๋ช ๋ค๋ฅธ ๋ฉ์๋)๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค
requestAnimationFrame
์ฌ์ฉ๋ฒ์ setTimeout
์ฒ๋ผ ์ฝ๋ฐฑ ํจ์ ๋ด๋ถ์์ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๋ ๋ฐฉ์์ด๋ค.
๋ธ๋ผ์ฐ์ ๋ ์ ๋๋ฉ์ด์
์ ์ถ๋ ฅํ ๋ ๋ง๋ค requestAnimationFrame์ ๋ฑ๋ก๋ ์ฝ๋ฐฑ ํจ์๋ค์ ๋น๋๊ธฐ๋ก ํธ์ถํ๋ค.
const performAnimation = () => {
requestAnimationFrame(performAnimation)
}
requestAnimationFrame(performAnimation);
์ทจ์ํ๋ ค๋ฉด cancelAnimationFrame
์ ์ฌ์ฉํ๋ฉด ๋๋ค.
์๋๋ฉ์ด์
์ ์ทจ์ํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฐจ์งํ๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ ์ ์์ด ๊ผญ ์ทจ์ํด์ค์ผ ํ๋ค.
const performAnimation = () => {
if(์กฐ๊ฑด){
cancelAnimationFrame(performAnimation);
return;
}
requestAnimationFrame(performAnimation);
}
requestAnimationFrame(performAnimation);
requestAnimationFrame์ ์ฝ๋ฐฑ Function์๋ ํ ๊ฐ์ง ์ธ์๊ฐ ์๋๋ฐ, ์ด ์ธ์๋ timestamp์ด๋ค. ์ ๋๋ฉ์ด์ ์ด ์คํ๋ ์ดํ์ ์๊ฐ์ ๋ฆฌํดํ๋ค.
const performAnimation = (timestamp: DOMHighResTimeStamp) => {
requestAnimationFrame(performAnimation)
}
requestAnimationFrame(performAnimation);
ํ๋ก์ ํธ์์ ์ฌ์ฉํ ๋์๋ requestAnimationFrame callback Function์ ์๊ฐ ๊ฐ๊ฒฉ์ ์ฃผ๊ณ ์ถ์๋ค.
requestAnimationFrame์ ์๋์ ์ผ๋ก ๋ธ๋ผ์ฐ์ ์ฃผ์ฌ์จ์ ๋ง์ถฐ ์คํ๋๊ธฐ ๋๋ฌธ์, 50ms ์ ๊ฐ์ ํน์ ๊ฐ์ ์ฃผ์ง ๋ชปํ๋ค. ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด useAnimationFrame
ํ
์ ๋ง๋ค๊ณ , ์๊ฐ ์
๋ ฅ ์ ๊ฒฝ๊ณผ ์๊ฐ์ ์ฌ์ callbackFn์ด ์คํ๋๋๋ก ๊ตฌํํ๋ค.
import {useEffect, useRef, useState} from 'react'
import {useEffect, useRef} from 'react'
const useRequestAnimationFrame = (callbackFn: () => boolean, ms?: number) => {
const idRef = useRef<number | null>(null)
const startTimeRef = useRef<number | null>(null)
useEffect(() => {
const animationFn = (timestamp: DOMHighResTimeStamp) => {
// for throttle
if (!startTimeRef.current) {
startTimeRef.current = timestamp
}
const elapsed = timestamp - startTimeRef.current
if (!ms || elapsed >= ms) {
const isNext = callbackFn()
startTimeRef.current = timestamp
// if isNext is false, cancel the animation
if (!isNext) {
cancelAnimationFrame(idRef.current!)
return
}
}
requestAnimationFrame(animationFn)
}
idRef.current = requestAnimationFrame(animationFn)
return () => {
if (idRef.current) cancelAnimationFrame(idRef.current)
}
}, [callbackFn, ms])
return idRef.current
}
export default useRequestAnimationFrame
์ด๋ ์ฃผ์ํด์ผ ํ ์ ์, ์ ํํ๊ฒ 50ms๋ง๋ค ๋ถ๋ฌ์ง๋๊ฒ ์๋๋ผ๋ ์ ์ด๋ค. ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์๊ฒ ์ง๋ง ์ฃผ์ฌ์จ์ ๋ง์ถฐ ํธ์ถ๋๊ธฐ ๋๋ฌธ์,
๋ด ์ปดํจํฐ (60fps) ๊ธฐ์ค์ผ๋ก 16.6 * 3 ์ด๋ฉด ๊ฑฐ์ 50ms์ด๋ค.
๊ทธ๋์ 50ms ๊ธฐ์ค 3๋ฒ ~ 4๋ฒ์งธ ํธ์ถ ๋ง๋ค ์คํ๋๊ธฐ ๋๋ฌธ์ ์ค์ ์คํ ์๊ฐ์ 50ms ~ 66ms(50ms + 16ms)์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.