Language/JavaScript (WEB)

🌐 μ›Ή μ• λ‹ˆλ©”μ΄μ…˜ μ΅œμ ν™” requestAnimationFrame κ°€μ΄λ“œ

인파_ 2023. 4. 18. 09:55

requestAnimationFrame

μžλ°”μŠ€ν¬λ¦½νŠΈ μ›Ή μ• λ‹ˆλ©”μ΄μ…˜

μ›ΉνŽ˜μ΄μ§€μ˜ μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν• λ•Œ CSS의 animatointransition , transform 속성을 톡해 κ΅¬ν˜„ν•  μˆ˜λ„ μžˆμ§€λ§Œ, 보닀 μ‚¬μš©μžμ™€μ˜ λ³΅μž‘ν•œ μƒν˜Έμž‘μš©μ„ κ΅¬ν˜„ν•˜κ²Œ ν•˜κΈ° μœ„ν•΄ Javascript와 ν•¨κ»˜ μ‚¬μš©ν•˜μ—¬ μŠ€νƒ€μΌμ„ λ³€ν™”μ‹œν‚€λ„ ν•œλ‹€. 예λ₯Όλ“€μ–΄ νŠΉμ • μ˜μ—­μ„ ν΄λ¦­ν•˜κ±°λ‚˜ μ›ΉνŽ˜μ΄μ§€λ₯Ό μŠ€ν¬λ‘€ν• λ•Œ λ³€ν™”λ¬΄μŒν•œ μ• λ‹ˆλ©”μ΄μ…˜ μž‘μ—…λ“€μ΄ κ·ΈλŸ¬ν•˜λ‹€.

κ·Έλž˜μ„œ κ°„λ‹¨ν•˜κ³  κ·œμΉ™μ μΈ μ• λ‹ˆλ©”μ΄μ…˜μ€ CSS둜만 μš”μ†Œμ˜ μ’Œν‘œκ°’μ΄λ‚˜ μŠ€νƒ€μΌ 크기λ₯Ό λ³€ν™”μ‹œν‚€κ³ , μ„Έλ°€ν•œ μ‘°μž‘μ΄ ν•„μš”ν•œ μ• λ‹ˆλ©”μ΄μ…˜μ€ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μŠ€νƒ€μΌ 속성을 λ³€κ²½ μ‹œν‚€λŠ” νŽΈμ΄λ‹€. ν•˜μ§€λ§Œ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μŠ€νƒ€μΌ 속성을 λ³€ν™”μ‹œν‚€λŠ” 방법은 CSS보닀 (특히 λͺ¨λ°”μΌμ—μ„œ) μ„±λŠ₯이 쒋지 μ•Šλ‹€. λ”°λΌμ„œ μ–΄μ©”μˆ˜ 없이 μžλ°”μŠ€ν¬λ¦½νŠΈμ™€μ˜ μƒν˜Έ ν˜‘λ ₯이 ν•„μš”ν•  경우 이λ₯Ό μœ„ν•œ μ΅œμ ν™” 기법이 μ‘΄μž¬ν•œλ‹€.

이번 ν¬μŠ€νŒ…μ€ μ΄λŸ¬ν•œ μ• λ‹ˆλ©”μ΄μ…˜ κ΄€λ ¨ μ΅œμ ν™” API인 requestAnimationFrame() 에 λŒ€ν•œ μ‚¬μš©λ²•κ³Ό 원리λ₯Ό μ†Œκ°œν•΄λ³΄λŠ” μ‹œκ°„μ„ κ°€μ Έ 보겠닀. λΈŒλΌμš°μ €λΌλŠ” μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό μ „λ¬Έμ μœΌλ‘œ λ‹€λ£¨λŠ” ν”„λ‘ νŠΈμ—”λ“œ 개발자일 경우 이에 κ΄€ν•œ 지식은 거의 ν•„μˆ˜λΌκ³  λ³Ό 수 μžˆλ‹€.

 

λΈŒλΌμš°μ € λ Œλ”λ§ 단계

λΈŒλΌμš°μ €κ°€ 화면에 무언가λ₯Ό κ·Έλ¦¬κΈ°κΉŒμ§€λŠ” μ•„λž˜ 사진과 같이 게 3 λ‹¨κ³„λ‘œ λ‚˜λˆŒ 수 μžˆλ‹€.

λΈŒλΌμš°μ € λ Œλ”λ§ 단계

  • Javascript - μ• λ‹ˆλ©”μ΄μ…˜ 및 기타 μž‘μ—… 슀크립트λ₯Ό μˆ˜ν–‰ (DOM 생성)
  • Style - CSS κ·œμΉ™μ„ μ–΄λ–€ μš”μ†Œμ— μ μš©ν• μ§€ κ³„μ‚°ν•˜λŠ” ν”„λ‘œμ„ΈμŠ€ (CSSOM 생성)
  • Layout - λΈŒλΌμš°μ €λŠ” DOMκ³Ό CSSOM을 κ²°ν•©ν•˜μ—¬ κ°μ²΄λ“€μ˜ μœ„μΉ˜μ™€ 크기 등을 κ³„μ‚°ν•˜λŠ” λ Œλ” 트리(Render Tree)λ₯Ό 생성.
  • Paint(redraw) - λΈŒλΌμš°μ €λŠ” λ Œλ” 트리λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹€μ œλ‘œ 화면에 픽셀을 좜λ ₯ (객체가 μ‹€μ œ ν™”면에 κ·Έλ €μ§€λŠ” κ²ƒμ„ μ˜λ―Έ)
  • Composite - λΈŒλΌμš°μ €λŠ” ν™”면에 μΆœλ ₯λ˜λŠ” κ°μ²΄λ“€μ„ ν•©μ„±ν•˜μ—¬ μ΅œμ’… ν™”면을 μƒμ„±

ν˜„μž¬λŠ” μ € 단계 ν•˜λ‚˜ν•˜λ‚˜λ₯Ό μ—¬κΈ°μ„œλŠ” μžμ„Ένžˆ μ•Œμ•„λ³Ό ν•„μš”λŠ” μ—†κ³ , μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μ—˜λ¦¬λ¨ΌνŠΈλ₯Ό 무언가 λ³€ν™”λ₯Ό μ€€λ‹€λ©΄ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κ³  끝~ 이 μ•„λ‹ˆλΌ, μœ„μ™€ 같은 λ³΅μž‘ν•œ λžœλ”λ§ νŒŒμ΄ν”„λΌμΈ 단계λ₯Ό μ§€λ‚˜ 화면에 κ·Έλ¦°λ‹€λŠ” μ •λ„λ‘œλ§Œ μ•Œκ³  있으면 λœλ‹€.

 

λΈŒλΌμš°μ € ν”„λ ˆμž„

κ²Œμž„ λͺ¨λ‹ˆν„°λ₯Ό κ³ λ₯΄λ‹€λ³΄λ©΄ 60hz, 120hz 라고 λͺ¨λ‹ˆν„° μ£Όμ‚¬μœ¨μ„ λ“€μ–΄λ³Έ 적이 μžˆμ„ 것이닀. μ΄λŠ” 1μ΄ˆλ™μ•ˆ λͺ¨λ‹ˆν„° ν™”λ©΄μ˜ 좜λ ₯ λΉˆλ„λ₯Ό λ‚˜νƒ€λ‚΄λŠ” λ‹¨μœ„ 이닀. 그리고 보톡 hzλŠ” frameκ³Ό 관련이 μžˆλ‹€.

μš°λ¦¬κ°€ μ˜ν™”λ‚˜ μ• λ‹ˆλ©”μ΄μ…˜μ„ λ³΄λŠ” 것은 사싀 짧은 μ‹œκ°„ 간격에 μ΄μ–΄μ§€λŠ” μž₯면을 λ³΄λŠ” 것이닀. 이 각각의 μž₯면을 frame이라고 ν•œλ‹€. 즉, ν”„λ ˆμž„μ€ ν•œ μž₯의 사진이라 봐도 λ¬΄λ°©ν•˜λ‹€. 그리고 νŠΉμ • μ‹œκ°„ 내에 λ³΄μ—¬μ§€λŠ” frame 갯수λ₯Ό frame rate ν˜Ήμ€ frame per second μ€„μ—¬μ„œ fps라고 ν•œλ‹€. (κ²Œμž„μ„ 해보면 κ°€μž₯ 자주 λ“£λŠ” λ‹¨μœ„μΌ 것이닀)

보톡 μΈκ°„μ˜ λˆˆμ€ 1μ΄ˆμ— 60번 μž₯면이 λ„˜μ–΄κ°€μ•Ό λΆ€λ“œλŸ½λ‹€κ³  λŠλ‚€λ‹€κ³  ν•œλ‹€. κ·Έλž˜μ„œ ν˜„λŒ€ 기기듀은 μ‹œκ°μ μΈ 효과λ₯Ό μœ„ν•΄ μ΄ˆλ‹Ή 60번 화면을 λ‹€μ‹œ 그리도둝 기본적으둜 μ„€κ³„λœλ‹€. 이λ₯Ό 60fps ν˜Ήμ€ 60hz 라고 λΆˆλ¦¬μš°λŠ” μ΄μœ μ΄λ‹€.

frame

ꡳ이 FPSλ₯Ό μ„€λͺ…ν•˜κ³  HzκΉŒμ§€ μ„€λͺ…ν•˜λŠ” μ΄μœ λŠ” μ›Ή 화면에 λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜ μ›€μ§μž„ 효과λ₯Ό μ£ΌκΈ° μœ„ν•΄μ„  이 ν”„λ ˆμž„ λ‹¨μœ„μ— 맞게 섀계해야 되기 λ•Œλ¬Έμ΄λ‹€. μ΄ˆλ‹Ή 60개의 ν”„λ ˆμž„μ„ λ Œλ”λ§ ν•œλ‹€λŠ” 말은, 16.666 λ°€λ¦¬μ„Έμ»¨λ“œ(1000ms / 60fps) κ°„κ²©μœΌλ‘œ ν”„λ ˆμž„ 생성이 ν•„μš”ν•œ μ…ˆμ΄ λœλ‹€. λ”°λΌμ„œ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μ‚¬μš©μžμ—κ²Œ λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•˜λ €λ©΄ 16.6msλ°€λ¦¬μ΄ˆ λ§ˆλ‹€ μ½”λ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” μ‹μœΌλ‘œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.

 

타이머 ν•¨μˆ˜λ₯Ό μ΄μš©ν•œ μ• λ‹ˆλ©”μ΄μ…˜ μŠ€ν¬λ¦½νŒ…

λ”°λΌμ„œ μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ 일정 μ‹œκ°„λ§ˆλ‹€ μ½”λ“œλ₯Ό 반볡 ν˜ΈμΆœν•˜λŠ” λŒ€ν‘œμ μΈ λ°©λ²•μœΌλ‘œλŠ” 타이머 ν•¨μˆ˜μΈ setInterval() μ™€ setTimeout() 을 μ΄μš©ν•΄ 60ν”„λ ˆμž„μ— 맞게 μŠ€ν¬λ¦½νŒ…μ„ ν•œλ‹€λ©΄ μ•„λž˜ μ½”λ“œμ™€ 같이 ꡬ성할 μˆ˜κ°€ μžˆλ‹€.

const performAnimation = () => {
  /* μŠ€νƒ€μΌ μ‘°μ • 슀크립트 */
}

// 1μ΄ˆμ— 60번 λ¬΄ν•œ 반볡
setInterval(performAnimation, 1000 / 60)
const performAnimation = () => {
  /* μŠ€νƒ€μΌ μ‘°μ • 슀크립트 */
  
  setTimeout(performAnimation, 1000 / 60); // ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ λ‹€μ‹œ setTimeout을 ν˜ΈμΆœν•˜μ—¬ 반볡
}

setTimeout(performAnimation, 1000 / 60);

 

타이머 ν•¨μˆ˜μ˜ 문제점

κ·ΈλŸ¬λ‚˜ setInterval() μ™€ setTimeout() 의 λ¬Έμ œμ μ€ 주어진 μ‹œκ°„λ‚΄μ— λ™μž‘μ„ ν•  뿐 ν”„λ ˆμž„μ„ μ‹ κ²½ 쓰지 μ•Šκ³  λ™μž‘ν•œλ‹€λŠ” 점이닀. 타이머 ν•¨μˆ˜λŠ” ν”„λ ˆμž„ λ‹¨μœ„λ‘œ ν”„λ ˆμž„ μ‹œμž‘ μ‹œκ°„μ— 맞좰 싀행됨을 보μž₯ν•˜μ§€ λͺ»ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

만일 μ•„λž˜ 그림처럼 μ•½ 16ms κ°„κ²©μœΌλ‘œ ν”„λ ˆμž„ λ‹¨μœ„κ°€ μ§„ν–‰λ˜μ–΄μ•Ό ν•˜λŠ”λ°, λΈŒλΌμš°μ €κ°€ λ‹€λ₯Έ μž‘μ—… μˆ˜ν–‰μœΌλ‘œ 인해 μ§€μ—°λ˜μ–΄ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ 콜백 μ½”λ“œ 뢀뢄이 λ‹¨μœ„ μ€‘κ°„μ—μ„œ ν˜ΈμΆœλ˜μ—ˆλ‹€κ³  ν•˜μž. 

setInterval

μžλ°”μŠ€ν¬λ¦½νŠΈ 싀행에 μ˜ν•΄ λ¦¬ν”Œλ‘œμš°κ°€ μΌμ–΄λ‚˜ μœ„μ—μ„œ λ³Έ λΈŒλΌμš°μ € λ Œλ”λ§ 단계인 λ ˆμ΄μ•„μ›ƒ - 페인트 - ν•©μ„± 과정이 λ‹€μ‹œ μΌμ–΄λ‚˜κ²Œ λ˜λŠ”λ°, 그러면 ν”„λ ˆμž„μ΄ μƒμ„±λ˜μ§€ λͺ»ν•˜κ³  λˆ„λ½λ˜μ–΄ 버렀 1 ν”„λ ˆμž„μ΄ κΉŽμ—¬λ²„λ¦¬λŠ” ν˜„μƒμ΄ λ‚˜νƒ€λ‚˜κ²Œ λœλ‹€.

μ΄λŸ¬ν•œ ν˜„μƒμ΄ 일어날 수 μžˆλŠ” μ΄μœ λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ 콜 μŠ€νƒ(call stack)은 μ‹±κΈ€ μŠ€λ ˆλ“œ 이기 λ•Œλ¬Έμ΄λ‹€

ν”„λ ˆμž„μ΄ κΉŽμΈλ‹€λŠ” 것은 곧 ν”„λ ˆμž„ λ“œλžμ΄ μΌμ–΄λ‚˜ κ²°κ΅­ 화면이 λ²„λ²…μ΄κ²Œ λœλ‹€. μ΄λŸ¬ν•œ 지연(delay) λ°œμƒ 문제 λ•Œλ¬Έμ— λŒ€μ•ˆμœΌλ‘œ νƒ„μƒν•œ 것이 λ°”λ‘œ rAF(requestAnimationFrame) 이닀.


requestAnimationFrame

requestAnimationFrame ν•¨μˆ˜λŠ” μ‹œμŠ€ν…œμ΄ ν”„λ ˆμž„μ„ 그릴 μ€€λΉ„κ°€ 되면 μ• λ‹ˆλ©”μ΄μ…˜ ν”„λ ˆμž„μ„ ν˜ΈμΆœν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜ μ›ΉνŽ˜μ΄μ§€λ₯Ό 보닀 μ›ν™œν•˜κ³  효율적으둜 생성할 수 μžˆλ„λ‘ ν•΄μ€€λ‹€. μ‹€μ œ 화면이 κ°±μ‹ λ˜μ–΄ ν‘œμ‹œλ˜λŠ” 주기에 따라 ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•΄μ£ΌκΈ° λ•Œλ¬Έμ— μžλ°”μŠ€ν¬λ¦½νŠΈκ°€ ν”„λ ˆμž„ μ‹œμž‘ μ‹œ μ‹€ν–‰λ˜λ„λ‘ 보μž₯ν•΄μ£Όμ–΄ μœ„μ™€ 같은 λ°€λ¦Ό ν˜„μƒμ„ 방지해쀀닀.

μ•„λž˜ μ΄λ―Έμ§€λŠ” requestAnimationFrame λ₯Ό 톡해 μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•  λ•Œ, 각 ν”„λ ˆμž„μ΄ λΈŒλΌμš°μ €μ˜ ν”„λ ˆμž„ 주기에 λ§žμΆ”μ–΄ μΌμ •ν•œ μ‹œκ°„ κ°„κ²©μœΌλ‘œ λ Œλ”λ§λ¨μ„ 보여쀀닀. requestAnimationFrame ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜λ©΄ λΈŒλΌμš°μ €λŠ” λ‹€μŒ ν”„λ ˆμž„μ΄ 그렀지기 전에 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜λ„λ‘ μ˜ˆμ•½ν•˜κΈ° λ•Œλ¬Έμ— 각 ν”„λ ˆμž„μ΄ μ •ν™•νžˆ 16.6ms κ°„κ²©μœΌλ‘œ λ Œλ”λ§λ˜κ²Œ 되게 λœλ‹€.

requestAnimationFrame

See the Pen setInterval vs requestAnimationFrame by barzz12 (@inpaSkyrim) on CodePen.

이처럼 μ• λ‹ˆλ©”μ΄μ…˜ ν”„λ ˆμž„μ„ νŽ˜μΈνŒ…ν•  μ€€λΉ„κ°€ 됐을 λ•Œ ν˜ΈμΆœν•œλ‹€λŠ” 뢀뢄은, 이전 setInterval 방식이 μ€€λΉ„κ°€ λ˜μ§€ μ•Šμ•„λ„ νŽ˜μΈνŒ…μ„ μš”μ²­ν•˜λŠ” 것과 λŒ€λΉ„λ˜λ©΄μ„œ, 보닀 μ›ΉλΈŒλΌμš°μ €μ— μ΅œμ ν™” λ˜μ–΄ μžˆλ‹€κ³  말할 수 μžˆλ‹€. λ˜ν•œ μ§€μ—° 및 λΈ”λ‘œν‚Ή ν˜„μƒμ΄ 생기지 μ•Šμ•„ 보닀 λΆ€λ“œλŸ¬μš΄ μ• λ‹ˆλ©”μ΄μ…˜μ„ μ œκ³΅ν•œλ‹€.

 

requestAnimationFrame μž₯점

 

λ°±κ·ΈλΌμš΄λ“œ λ™μž‘ 쀑지

setInterval 같은 경우 λΈŒλΌμš°μ €μ˜ λ‹€λ₯Έ νƒ­ 화면을 λ³΄κ±°λ‚˜ λΈŒλΌμš°μ €κ°€ μ΅œμ†Œν™”λ˜μ–΄ μžˆμ„ λ•Œ 계속 타이머가 λŒμ•„ μ½œλ°±μ„ ν˜ΈμΆœν•˜κΈ° λ•Œλ¬Έμ— μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€ λ‚­λΉ„λ₯Ό μ΄ˆλž˜ν•˜κ³  λΆˆν•„μš”ν•œ μ „λ ₯을 μ†Œλͺ¨ν•˜κ²Œ λ§Œλ“ λ‹€.

반면 requestAnimationFrameλŠ” νŽ˜μ΄μ§€κ°€ λΉ„ν™œμ„±ν™” 된 μƒνƒœμ΄λ©΄ νŽ˜μ΄μ§€ ν™”λ©΄ 그리기 μž‘μ—…λ„ λΈŒλΌμš°μ €μ— μ˜ν•΄ μΌμ‹œ μ€‘μ§€λ¨μœΌλ‘œ CPU λ¦¬μ†ŒμŠ€λ‚˜ 배터리 수λͺ…을 λ‚­λΉ„ν•˜μ§€ μ•Šκ²Œ λœλ‹€.

 

λ””μŠ€ν”Œλ ˆμ΄ μ£Όμ‚¬μœ¨μ— 맞게 호좜 횟수

이 νŠΉμ§•μ€ μ΅œμ ν™”λœ μ›Ή μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•˜λŠ”λ° μžˆμ–΄ setInterval κ³Ό requestAnimationFrame 의 결정적인 차이점이라고 말할 수 μžˆλ‹€. μ§€κΈˆκΉŒμ§€ 1μ΄ˆμ— 60번 ν˜ΈμΆœν•œλ‹€κ³  μ¨μ™”μ§€λ§Œ, μ •ν™•νžˆ λ§ν•˜μžλ©΄ μžμ‹ μ˜ λͺ¨λ‹ˆν„°κ°€ 60hz μ£Όμ‚¬μœ¨ λͺ¨λ‹ˆν„°μΌ κ²½μš°μ΄λ‹€. 즉, μ›ΉλΈŒλΌμš°μ €λŠ” λ””μŠ€ν”Œλ ˆμ΄μ˜ μ£Όμ‚¬μœ¨μ„ λ”°λ₯΄λ©° 만일 144hz μ£Όμ‚¬μœ¨ 고사양 λͺ¨λ‹ˆν„°μΌ 경우 rAF μ—­μ‹œ 1μ΄ˆμ— 144번 호좜되게 λœλ‹€.

μ •λ¦¬ν•˜μžλ©΄ setInterval μ—μ„œλŠ” 콜백 ν•¨μˆ˜ 호좜 간격을 μ‹œκ°„μ„ 지정해주고 호좜 횟수λ₯Ό μ„€μ •ν•˜μ§€λ§Œ, rAFμ—μ„œλŠ” λͺ¨λ‹ˆν„°μ˜ μ£Όμ‚¬μœ¨μ„ κ·ΈλŒ€λ‘œ λ”°λ₯΄κ²Œ λ˜μ–΄ μ΅œμ ν™” λ˜μ–΄ μžˆλ‹€λŠ” 것이닀. 그리고 μ΄λŸ¬ν•œ νŠΉμ„± λ•Œλ¬Έμ— rAFλŠ” 슀크둀 이벀트 μ΅œμ ν™” κΈ°λ²•μœΌλ‘œλ„ 쓰이기도 ν•œλ‹€.

requestAnimationFrame

 

Animation frames νμ—μ„œ 처리

requestAnimationFrame(rAF) ν•¨μˆ˜λ„ setTimeout μ΄λ‚˜ 여타 이벀트 ν•Έλ“€λŸ¬μ™€ 같이 "μ• λ‹ˆλ©”μ΄μ…˜ ν”„λ ˆμž„"을 그리기 μœ„ν•œ 콜백 ν•¨μˆ˜λ₯Ό λ“±λ‘ν•˜κ³  비동기 task둜 λΆ„λ₯˜ν•˜μ—¬ μ²˜λ¦¬λœλ‹€. μ΄λ•Œ μ€‘μš”ν•œ νŠΉμ§•μ€ rAFλŠ” 일반적인 task queueκ°€ μ•„λ‹ˆλΌ animation frameμ΄λΌλŠ” λ³„κ°œμ˜ queueμ—μ„œ μ²˜λ¦¬λœλ‹€λŠ” 점이닀.

Animation frames 은 λΈŒλΌμš°μ €μ˜ λ Œλ”λ§ 엔진이 λ‹€μŒ ν”„λ ˆμž„μ„ 그리기 전에 μ‹€ν–‰ν•΄μ•Ό ν•˜λŠ” rAF에 λ“±λ‘ν•œ 콜백 ν•¨μˆ˜λ“€μ„ λ‹΄λŠ” 큐이닀. λ³„λ„μ˜ νμ—μ„œ μ μž¬λ˜μ–΄ 이벀트 루프에 μ˜ν•΄ 꺼내지기 λ•Œλ¬Έμ— 싀행이 λ’€μ³μ§€κ±°λ‚˜ ν•˜λŠ” ν˜„μƒμ„ κ°μ†Œν• μˆ˜κ°€ μžˆλ‹€. 단, requestAnimationFrame도 λΈŒλΌμš°μ €μ˜ CPUλ‚˜ GPU μ‚¬μš©λŸ‰ μ—¬λΆ€ 등에 따라 콜백 ν•¨μˆ˜ 싀행이 밀릴 μˆ˜λ„ μžˆλ‹€.

requestAnimationFrame

μœ„λŠ” λΈŒλΌμš°μ €μ˜ μžλ°”μŠ€ν¬λ¦½νŠΈ 이벀트 루프 주기의 싀행을 그림으둜 ν‘œν˜„ν•œ 것이닀. 그림을 처음 보면 되게 눈 μ•„ν”„λ‘œ λ³΅μž‘ν•˜κ²Œ 보일 수 μžˆλŠ”λ° κ°„λ‹¨νžˆ μ •λ¦¬ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

  • Task queue 1 - 이벀트 콜백 ν•¨μˆ˜, setTimeout 및 setInterval 비동기 콜백 ν•¨μˆ˜λ₯Ό 처리
  • Task queue 2 - Task queue 1에 μžˆλŠ” λͺ¨λ“  콜백 ν•¨μˆ˜λ“€μ΄ μ‹€ν–‰λœ ν›„ μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λŠ” λ‹€μŒ 콜백 ν•¨μˆ˜λ“€μ΄ λ“€μ–΄κ°„λ‹€.
  • Microtask queue - Promise 객체, Mutation Observer 객체λ₯Ό 처리 (κ°€μž₯ μš°μ„ μˆœμœ„κ°€ λ†’μŒ)
  • Animation frames - requestAnimationFrame 와 같이 λΈŒλΌμš°μ € λ Œλ”λ§κ³Ό κ΄€λ ¨λœ νƒœμŠ€ν¬ 처리 

κ°„λ‹¨ν•œ 이벀트 루프 호좜 μŠ€νƒλ§Œμ„ λ³Έ λ…μžλΆ„λ“€μ€ λ‚œμ΄λ„κ°€ 수직 μƒμŠΉν•˜λŠ” λŠλ‚Œμ„ 받을 수 μžˆκ² μ§€λ§Œ, λ˜‘κ°™μ€ 비동기 ν•¨μˆ˜λΌλ„ μ΄λ ‡κ²Œ 처리 μš°μ„ μˆœμœ„ 큐가 λ³„λ„λ‘œ μžˆλ‹€λŠ” 정도 μ•Œκ³  있으면 λœλ‹€. 

Promise.then κ²°κ³Όκ°€ setTimeout보닀 μš°μ„  λœλ‹€λŠ” μ†Œλ¦¬λ„ λ°”λ‘œ 이 λ•Œλ¬Έμ΄λ‹€.

 

requestAnimationFrame μ‚¬μš©λ²•

requestAnimationFrame μ‚¬μš©λ²•μ€ setTimeout 처럼 콜백 ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ μž¬κ·€ 호좜 ν•˜λŠ” μ‹μœΌλ‘œ κ΅¬μ„±ν•˜λ©΄ λœλ‹€. λΈŒλΌμš°μ €λŠ” μ• λ‹ˆλ©”μ΄μ…˜ ν”„λ ˆμž„μ„ 좜λ ₯ν•  λ•Œλ§ˆλ‹€ requestAnimationFrame 에 λ“±λ‘λœ 콜백 ν•¨μˆ˜λ“€μ„ λΉ„λ™κΈ°λ‘œ ν˜ΈμΆœν•˜μ—¬, μ• λ‹ˆλ©”μ΄μ…˜μ„ λΆ€λ“œλŸ½κ²Œ 좜λ ₯ν•΄μ€€λ‹€. λŒ€μ‹  차이점은 setTimeout 은 타이머λ₯Ό 지정해주어야 ν•˜μ§€λ§Œ, requestAnimationFrame 은 ν”„λ ˆμž„ λ‹¨μœ„λ‘œ λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ— λ³„λ„μ˜ 반볡 ν”Œλž˜κ·Έκ°€ ν•„μš” μ—†λ‹€λŠ” 점이닀.

const performAnimation = () => {
  /* μŠ€νƒ€μΌ μ‘°μ • 슀크립트 */
  
  requestAnimationFrame(performAnimation) // ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ λ‹€μ‹œ requestAnimationFrame을 ν˜ΈμΆœν•˜μ—¬ 반볡
}

requestAnimationFrame(performAnimation);

그리고 setTimeout 을 μ·¨μ†Œν•˜κΈ° μœ„ν•΄ clearTimout 을 μ‚¬μš©ν•˜λ“―μ΄, requestAnimationFrame λ₯Ό μ·¨μ†Œν•˜λŠ” λ°©λ²•μœΌλ‘œ cancelAnimationFrame λ₯Ό μ‚¬μš©ν•œλ‹€. μ•„λž˜μ™€ 같이 νŠΉμ •ν•œ μ‘°κ±΄μ—μ„œ μ• λ‹ˆλ©”μ΄μ…˜μ„ μ€‘μ§€ν•˜κ³  μ‹Άμ„λ•Œ μ΄μš©ν•˜λ©΄ λœλ‹€.

let raf; // requestAnimationFrame을 담을 λ³€μˆ˜

const performAnimation = () => {
    /* μŠ€νƒ€μΌ μ‘°μ • 슀크립트 */
	
    // νŠΉμ •ν•œ 쑰건일 경우 rafλ₯Ό μ€‘μ§€ν•˜κ³  콜백 μ’…λ£Œ
    if(쑰건) {
    	cancelAnimationFrame(raf);
		return;
    }

    raf = requestAnimationFrame(performAnimation) // ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ λ‹€μ‹œ requestAnimationFrame을 ν˜ΈμΆœν•˜μ—¬ 반볡
}

requestAnimationFrame(performAnimation);

 

requestAnimationFrame 예제

λ‹€μŒμ€ λ‘œμΌ“ μš”μ†Œκ°€ 있고, requestAnimationFrameλ₯Ό 톡해 yμ’Œν‘œ 값을 μ‘°μ •ν•˜μ—¬ μœ„λ‘œ μ˜¬λ¦¬λŠ” μ½”λ“œλ₯Ό 반볡 ν˜ΈμΆœν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜μ„ μ‹€ν–‰ν•˜λŠ” μ˜ˆμ œμ΄λ‹€.

<img class="rocket" src="https://www.freeiconspng.com/thumbs/rocket-png/rocket-png-1.png" alt="λ‘œμΌ“ μ• λ‹ˆλ©”μ΄μ…˜">
<count class="value">0</count>
<button>λ‹€μ‹œ μ‹œμž‘</button>
const rocket = document.querySelector('.rocket');
const value = document.querySelector('.value');
let yPos = 0;
let rafId;

// 콜백 ν•¨μˆ˜
const render = () => {
  yPos += 2; // y μ’Œν‘œ 증가

  rocket.style.transform = `translateY(${-yPos}px)`; // λ‘œμΌ“μ„ μœ„λ‘œ 올리기
  value.innerHTML = yPos; // μΉ΄μš΄ν„° ν‘œμ‹œ

  // λ§Œμ•½ λ‘œμΌ“ μœ„μΉ˜κ°€ 일정 yμ’Œν‘œκ°’μΌ 경우 requestAnimationFrame μ’…λ£Œ
  if (yPos >= 500) {
    cancelAnimationFrame(rafId);
    return;
  }

  rafId = requestAnimationFrame(render); // rAF 반볡 호좜
}

requestAnimationFrame(render); // μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμž‘
// 만일 λ‘œμΌ“μ— 마우슀 μ»€μ„œλ₯Ό μ˜¬λ €λ†“μœΌλ©΄ μ• λ‹ˆλ©”μ΄μ…˜ 쀑지
rocket.addEventListener('mouseover', () => {
  cancelAnimationFrame(rafId);
});

// 만일 λ‘œμΌ“μ— 마우슀 μ»€μ„œλ₯Ό λΉΌλ©΄ μ• λ‹ˆλ©”μ΄μ…˜ μž¬μ‹œμž‘
rocket.addEventListener('mouseout', () => {
  requestAnimationFrame(render);
});

See the Pen requestAnimationFrame by barzz12 (@inpaSkyrim) on CodePen.

 

μ• λ‹ˆλ©”μ΄μ…˜ 타이머 μ΄μš©ν•˜κΈ°

requestAnimationFrame ν•¨μˆ˜μ˜ 콜백 ν•¨μˆ˜μ˜ 인자둜 λ§€κ°œλ³€μˆ˜λ₯Ό μ „λ‹¬ν•˜λ©΄ requestAnimationFrame ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜κΈ° μ‹œμž‘ν•œ μ΄ν›„μ˜ μ‹œκ°„μ„ 얻을 수 μžˆλ‹€. 이λ₯Ό μ΄μš©ν•΄ μ• λ‹ˆλ©”μ΄μ…˜ μˆ˜ν–‰ μ‹œκ°„μ„ ꡬ해 νŠΉμ • νƒ€μ΄λ¨ΈμΌλ•Œ 슀크립트λ₯Ό μ‹€ν–‰ν•˜λŠ” μ‹μ˜ μ‘μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

let startTime;

// requestAnimationFrame의 콜백 ν•¨μˆ˜μ— λ§€κ°œλ³€μˆ˜λ₯Ό μ •μ˜
const draw = (timestamp) => {
    if (!start) start = timestamp; // μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμž‘ μ‹œκ°„

	currentTime = timestamp - startTime;

	// μ• λ‹ˆλ©”μ΄μ…˜μ΄ μ‹€ν–‰λœμ§€ 2μ΄ˆκ°€ μ§€λ‚˜λ©΄..
	if(currentTime > 2000) { 
        console.log('END');
        return;
    }

	requestAnimationFrame(draw);
}

requestAnimationFrame(draw);

# 참고자료

https://youtu.be/9XnqDSabFjM

https://dribbble.com/shots/1945400-FPS-frames-per-second

https://jbee.io/web/optimize-scroll-event/