Request Animation Frame(rAF)
Do not use setInterval for animation!
1. Transition π©βπ»
λΈλΌμ°μ κ° μ λλ©μ΄μ
μ ννν μ μλ λ°©λ²μ΄ 무μμ΄ μμκΉ? κ°μ₯ κΈ°λ³Έμ μ΄κ³ μ¬μ΄ κ²! λ°λ‘ CSS μ transition
μ μ¬μ©νλ κ²μ΄λ€.
transition μ μ±λ₯λ μ’κ³ , μ¬μ©νκΈ° μ¬μ λ¨μν μ λλ©μ΄μ
ꡬνμ κ°μ₯ λ§μ΄ μ¬μ©λλ λ°©λ²μ΄λ€. νμ§λ§ μ¬κΈ°μλ ν κ°μ§ λ¬Έμ μ μ΄
μ‘΄μ¬νλ€.
λ°λ‘ μ λλ©μ΄μ
μ μ©μ μν΄μλ hover
κ° λ°μνλ€κ±°λ class
κ° λ³κ²½λλ€κ±°λ νλ λ± μ¬μ©μμ μΈν°λμ
μ΄ λ°μνκ±°λ JavaScript
μ½λμ μν΄ class κ° λ³κ²½λλ€κ±°λ ν΄μ μ μ©λμ΄μΌ νλ CSS μ λ³κ²½μ΄ λ°μλμ΄μΌνλ€.
μ¦, μ λλ©μ΄μ
μ΄ μλνκΈ° μν΄μλ μΈλΆ κ°μ
μ΄ λ°λμ νμνλ€. κ·Έλ¦¬κ³ μ λλ©μ΄μ
μ λ°λ³΅νκ³
μΆμ κ²½μ° setInterval μ λμμ λ°μμΌ νκ³ , λ κ°μ μ λλ©μ΄μ
μ μ΄μ΄μ μ°μμΌλ‘ μλμν€κ³ μΆμ κ²½μ° μμ setTimeout μ λμμ
λ°μμΌνλλ°, JavaScript κ° μ±κΈμ€λ λμ΄λ€λ³΄λ μ νν μ λλ©μ΄μ
ꡬνμ΄ λμ§ μμ μ μμ λΏ μλλΌ λ¨μν μ λλ©μ΄μ
μ°κ²°μ μν΄μ
CSS λ JavaScript λ λ무 λμ‘ν΄μ§κ²λλ λ¬Έμ κ° μλ€.
λ°λΌμ μ¬μ©μ μΈν°λμ μ λ°μνλ λ¨μν μ λλ©μ΄μ ν¨κ³Όλ₯Ό μ€ λλ λ§€μ° ν¨μ¨μ μ΄μ§λ§ κ·Έ μΈ κ²½μ°μ μ¬μ©νκΈ° νλ€λ€λ λ¨μ μ΄ μλ€.
2. setInterval π©βπ»
1. Problems of the setInterval
κ·Έλ λ€λ©΄ setInterval
μ μ¬μ©νλ κ²μ μ΄λ¨κΉ? μ λλ©μ΄μ
ν¨κ³Όλ₯Ό μ€λ‘μ§ JavaScript λ§μΌλ‘ μμ±νκΈ° λλ¬Έμ μ λλ©μ΄μ
λ°λ³΅μ΄λ
μ°κ²° λͺ¨λ μ¬μ©μ μΈν°λμ
μμ΄ μ½λλ§μΌλ‘ λͺ¨λ κ²μ μ μ΄ν μ μλ€λ μ₯μ μ κ°λλ€.
νμ§λ§ setInterval μ μΆμ²λλ λ°©λ²μ΄ μλλ€. μ κ·Έλ΄κΉ?
- νλ μ μμ€ λ°μ κ°λ₯μ±μ΄ λλ€.
- νλμ¨μ΄ κ°μμ μ¬μ©νμ§ λͺ»νλ€.
μ°μ 첫 λ²μ§Έ, κ°μ₯ ν° λ¬Έμ κ° νλ μ μμ€μ΄λ€. JavaScript κ° μ±κΈμ€λ λμΈ κ²κ³Ό κ΄λ ¨μ΄ μλ€. HTML λ° CSS νμΌμ μ½μ΄μ νμ± νκ³
Object Model(DOM, CSSOM) νΈλ¦¬λ₯Ό μμ± ν λ μ΄μμμ μ‘κ³ μ€νμΌμ μ μ©νλ©΄ λ λλ§μ΄ μλ£λλ€. κ·Έλ°λ° μ λλ©μ΄μ
μ΄ μ μ©λλ€λ κ²μ
λ λλ§ κ³Όμ μ€ λ μ΄μμμ μ‘κ³ μ€νμΌμ μ μ©νλ Reflow
μ Repaint
κ° μ§μμ μΌλ‘ λ€μ νΈμΆλλ μ€νλλ μνλ₯Ό μλ―Ένλ€.
2. Display Hertz
νλμ¨μ΄ κ°μμ μ§μνμ§ μμμ μ±λ₯μ΄ μ’μ§ λͺ»νλ€λ κ²μ μ½κ² μ΄ν΄ν μ μλ€. νμ§λ§ νλ μ μμ€μ΄ λ°μν κ°λ₯μ±μ΄ λλ€λ κ²μ μμΌκΉ?
λΈλΌμ°μ λ μλ μ λλ©μ΄μ
κ°μ μμ
μ μΌλν΄λκ³ λ§λ€μ΄μ§μ§ μμμλ€. μ μ μΈ λ°μ΄ν°λ₯Ό μΆλ ₯νλλ‘ λ§λ€μ΄μ‘λ€λ κ²μ΄λ€. 그리κ³
JavaScript λ₯Ό κΈ°λ³Έ μΈμ΄λ‘ μ±ννλ©΄μ λ©ν°μ€λ λ νκ²½μ΄ λΆκ°λ₯ν΄μ‘λ€. μ΄λ° λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ Service Worker
κ° λμ
λμμ§λ§
λΈλΌμ°μ μ λ λλ§μ λ©μΈμ€λ λμμ λμμΌ νκΈ° λλ¬Έμ λ¬΄κ±°μ΄ μμ
μ Worker λ₯Ό ν΅ν΄ λ릴 μ μμ΄λ λ©μΈμ€λ λμ interrupt λ₯Ό
λ§μ μλ μλ€.
νλ μκ³Ό μ£Όμ¬μ¨μ΄ μ΄λ€ κ΄κ³μ μκΈΈλ μ΄κ²μ΄ μ λλ©μ΄μ μ μ€μν μν₯μ λ―ΈμΉλ κ²μΈμ§ TVμ μ¬λ‘λ₯Ό 보μ. μνλ μλ λΆν° μ§κΈκΉμ§ λλΆλΆ 23.976fps λ₯Ό μ¬μ©νκ³ μλ€. μ½ 24fps λΌλ λ§μ΄λ€. κ·Έλ¬λ©΄ λμ€νλ μ΄ μ£Όμ¬μ¨μ΄ 24fps μ μ μλ°°λ₯Ό κ°μ ΈμΌ νλ μ μμ€μ΄ μλ€.
κ·Έλ°λ° TV λ°©μ‘ μμ₯μ΄ λμ§νΈλ‘ λμ΄μ€λ©΄μ NTSC λ°©μμ μ¬μ©νλ μ°λ¦¬λλΌμ λ―Έκ΅μ 30fps λ₯Ό μ¬μ©νκ³ PAL & SECAM λ°©μμ μ¬μ©νλ μ λ½μ 25fps λ₯Ό μ¬μ©νκ³ μλ€. μμΌκΉ? μ°λ¦¬μ λ―Έκ΅μ κ΅λ₯ μ λ ₯ μ£Όνμλ 60Hz κ³ , μ λ½μ 50Hz λ₯Ό μ¬μ©νλ€. κ΅λ₯ μ λ ₯μ μ£Όνμμ λμ€νλ μ΄ μ£Όμ¬μ¨μ λκΈ°ν μν€λ©΄ λΉμ©μ μ€μΌ μ μλ μ μ μ°©μν κ²μ΄λ€. κ°νΉ κ°λ€ 50Hz λͺ¨λν°λ μμ§λ§ λλΆλΆμ λμ€νλ μ΄κ° 60Hz κ° νμ€μΌλ‘ μ리 μ‘μ μ΄μ λ€.
μ¬κΈ°μ λ¬Έμ κ° λλ κ²μ΄ 무μμΌκΉ? λμ§νΈ λ°©μ‘μ΄ μλ μνλ₯Ό TVλ‘ λ³Ό λ λ°μνλ€. 60Hzλ 1μ΄μ 60λ² νλ μμ λ³κ²½ν μ μλ€λ λ§μΈλ° μνλ μ½ 24fps μ΄κΈ° λλ¬Έμ μ μλ°°λ₯Ό λ§μΆ μ μλ€. κ²°κ΅ κ°κ°μ νλ μμ΄ 3λ², 2λ², 3λ², 2λ²β¦ μ΄λ°μμΌλ‘ λΏλ €μ ΈμΌ 60Hz 쑰건μ λ§μΆ μ μλ κ²μ΄λ€. κ·Έλ¬λ©΄ λ¬΄μ¨ μΌμ΄ λ°μν κΉ? μμμ΄ μ΄μν μκ°μ΄ λνλκ² λλ€. μκ·Έλλ 24fps λΌλ νλ μ μμΉκ° λμ μμΉκ° μλλ° νλ μ λΆλ°° λ§μ μ μλ°°κ° λ§μ§ μμΌλ μμμ΄ λ§€λλ½μ§ μμ λ¬Έμ κ° λ°μνλ€.
κ·Έλμ TV λ ν¨λμ 120Hz μ μ£Όμ¬μ¨μ λμ νλ€. κ·Έλ¬λ©΄ 24fps μ μ μλ°°κ° λκΈ° λλ¬Έμ΄λ€. κ²λ€κ° AD 보λλ₯Ό μ¬μ©ν΄ νλ μ 보κ°κΉμ§ νλ λ± μμμ λΆλλ½κ² νκΈ° μν΄ μ΅μ μ λ€νκ³ μλ€.
νλ μ λ°°λΆμ΄ κ³ λ₯΄κ² λμ§ μλλ€λ κ²μ
맀λλ¬μ΄ νλ©΄μ λ³Ό μ μλ€λ κ²μ μλ―Ένλ€.
3. In Browser
μ΄μ λ€μ λΈλΌμ°μ λ‘ λμμ보μ. TV μ¬λ‘λ‘λΆν° 60Hz μ λͺ¨λν°μμ κ°μ₯ μ΅μ μ μ λλ©μ΄μ μ 60fps λ₯Ό κ°μ ΈμΌ ν¨μ μ μ μλ€. κ·Έ λ€μ λμμΌλ‘λ μ μλ°°λ₯Ό κ°μ§ μ μλ κ° μ€ κ°μ₯ ν° κ°μΌλ‘ 30fps κ° λμμ΄ λ μ μλ€.
κ·Έλ¬λ©΄ setInterval λ‘ 60fps λ₯Ό λ§μ‘±μν€λ €λ©΄ μ΄λ»κ² ν΄μΌν κΉ?
60fps λ₯Ό λ€μ§μ΄λ³΄μ.
\( \frac{1\, \text{s}}{60\, \text{frames}} = \frac{1000\, \text{ms}}{60\, \text{frames}} \approx 16.6\, \text{ms} \)
κ° νλ μκ° κ°κ²©μ΄ μ½ 16.6ms κ° λμ΄μΌ νλ€. λ§μ½ 30fps λ‘ μ λλ©μ΄μ μ λΏλ¦¬κ³ μΆλ€λ©΄ νλ μ κ°κ²©μ 2λ°°μΈ 33.2ms κ° λλ κ²μ΄λ€.
setInterval μ΄ μ νν μ£ΌκΈ°λ§λ€ μ€νλλ€λ©΄ μλ¬΄λ° λ¬Έμ μμ΄ μ λλ©μ΄μ μ μΆλ ₯ν μ μλ€. νλμ¨μ΄ κ°μμ΄ μ§μλμ§ μμ λ€μ μ±λ₯μ μν₯μ λ―ΈμΉλλΌλ μ λλ©μ΄μ ꡬν μ체λ μ μμ μΌλ‘ ν μ μλ€λ λ§μ΄ λλ€.
κ·Έλ°λ° λ¬Έμ λ setInterval
μ λΈλΌμ°μ μ λ λλ§ μ£ΌκΈ°μ 무κ΄νκ² μλνλ€λ κ²μ΄λ€. μ¦, λμ€νλ μ΄ μ£Όμ¬μ¨μ κ³ λ €νμ§ μκ³ μ€νλλ€λ
λ§μ΄λ€. μ΄κ² μ λ¬Έμ κ° λ κΉ?
μ°μ 첫 λ²μ§Έ λ¬Έμ λ λͺ¨λ λμ€νλ μ΄κ° 60Hz λ₯Ό μ¬μ©νμ§ μλλ€. μ¬μ©μκ° μμλ‘ μ£Όμ¬μ¨μ λ³κ²½νμ μλ μκ³ , 50Hz λΌλκ° 75Hz, κ·Έ μΈ
κ³ μ£Όμ¬μ¨λ‘ 90Hz, 120Hz, 144Hz μ κ°μ΄ λμ€νλ μ΄λ§λ€ λ€μν μ£Όμ¬μ¨μ κ°λλ€. λ λ²μ§Έ λ¬Έμ λ setInterval
μ΄
Macrotask Queue λΌ Microtask Queue λ± λ€λ₯Έ μμ
μ΄ μ€νμ λΌμ΄λ€ κ²½μ° λμ€νλ μ΄ μ£Όμ¬μ¨κ³Ό setInterval μ μ£ΌκΈ°κ° λ¬λΌμ§λ€λ κ²μ΄λ€.
3. Synchronize with Display Hertz π©βπ»
μμ κ°μ λ¬Έμ λ₯Ό ν΄κ²°ν΄ μνν μ λλ©μ΄μ
μ νννκΈ° μν λ°©λ²μ΄ λ κ°μ§ μ‘΄μ¬νλ€. νλλ CSS μ animation
μ μ¬μ©νλ κ²μ΄κ³ ,
λ€λ₯Έ νλλ JavaScript μ requestAnimationFrame
μ μ¬μ©νλ κ²μ΄λ€. μ΄ λμ λΈλΌμ°μ κ° μ 곡νλ API λ₯Ό μ¬μ©ν΄ μ΅μ νλ
μ λλ©μ΄μ
μ λ§λ€μ΄λ΄λλ° νΉμ§μ λ€μκ³Ό κ°λ€.
- λμ€νλ μ΄ μ£Όμ¬μ¨(=λΈλΌμ°μ λ λλ§ μμ§ μ€ν μ£ΌκΈ°)μ λκΈ°ν λΌ μ€νλλ―λ‘ νλ μ μμ€ λ°μ κ°λ₯μ±μ΄ μ λ€.
- λμ€νλ μ΄ μ£Όμ¬μ¨κ³Ό λκΈ°νλλ―λ‘ μ΅μ μ μ λλ©μ΄μ νλ μ ꡬνμ΄ κ°λ₯νλ€.
- νλμ¨μ΄ κ°μμ μ¬μ©ν μ μλ€.
κ·Έλ κΈ° λλ¬Έμ μ΄ λ λ°©μμ λ λ°μ΄λ μ λλ©μ΄μ μ μ 곡ν μ μλ κ²μ΄λ€.
1. CSS - animation
1 ) Animation Properties
CSS μ animation
μ΄ μ 곡νλ properties λ λ€μκ³Ό κ°λ€.
animation-name
:@keyframes
μ΄λ¦μ μ§μ νλ©° νμκ°μ΄λ€.animation-duration
: ms, s λ¨μλ‘ μ§μ νλ©° νμκ°μ΄λ€.animation-timing-function
: μ λλ©μ΄μ νμ΄λ° ν¨μλ₯Ό μ§μ νλ€. κΈ°λ³Έκ°μease
λ€.animation-delay
: ms, s λ¨μλ‘ μ λλ©μ΄μ μμ λλ μ΄λ₯Ό μ§μ νλ€.animation-iteration-count
: κΈ°λ³Έκ°μ1
μ΄λ©°infinite
μ μ£Όλ©΄ 무ν μ¬μμ΄ κ°λ₯νλ€.animation-direction
: κΈ°λ³Έκ°μnormal
μ΄λ©°,reverse
λ μλ°©ν₯,alternate
λ μ λ°©ν₯ λ°μ΄μ€,alternate-reverse
λ μλ°©ν₯ λ°μ΄μ€λ₯Ό μ§μ νλ€.animation-fill-mode
: μ λλ©μ΄μ μ μ© νλκ°μΌλ‘ κΈ°λ³Έκ°μnormal
μ΄λ€.
- normal: λκΈ° -> μμ -> μ’ λ£ -> λκΈ°
- forwards: λκΈ° -> μμ -> μ’ λ£
- backwards: μμ -> μ’ λ£ -> λκΈ°
- both: μμ -> μ’ λ£animation-play-state
: κΈ°λ³Έκ°μrunning
μ΄λ©°paused
λ₯Ό μ£Όμ΄ μΌμμ μ§ μν¬ μ μλ€.
μ°Έκ³ λ‘ setInterval μ clearInterval
λ‘ λ°λ³΅μ μ€λ¨ν μ μλ―μ΄ requestAnimationFrame μ cancelAnimationFrame
λ‘ μ λλ©μ΄μ
μ€λ¨μ΄ κ°λ₯λ€. νμ§λ§ μ΄κ²μ λ°λ³΅μ μ€λ¨μν€λ κ²μΌ λΏ μ¬κ°ν μ μλ κΈ°λ₯μ μ‘΄μ¬νμ§
μλλ€. λ°λΌμ μ¬κ°λ₯Ό μν΄μλ λ€μ μμν λΌ λ¨μ μκ°κ³Ό λ³νλλ§ μ λλ©μ΄μ
μ μ§ννλλ‘ κ³μ°μ μ§μ ν΄μ£Όμ΄μΌνλ€.
λ°λ©΄ animation μ animation-play-state
λ κ°μ λ°κΎΈλ κ² λ§μΌλ‘ μΌμμ μ§ λ° μ¬κ°λ₯Ό ν μ μλ€.
κ·Έλ¦¬κ³ μ properties λ¨μΆ μμ±μΌλ‘ animation
μ μ¬μ©νλ©΄ λͺ¨λ μ€μ κ°μ ν λ²μ μ§μ ν μ μλ€.
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
2 ) @keyframes
μ properties λ μ λλ©μ΄μ
μ μ© μ€νμΌμ μ§μ νλ λΆλΆμ μ‘΄μ¬νμ§ μλλ€. μ¦, μ λλ©μ΄μ
μ€μ κ°μ μ§μ νλ μ΅μ
μΌλ‘, μ€μ μ€νμΌμ
@keyframes
λ₯Ό μ¬μ©ν΄ μ§μ νλ€. λ°λΌμ animation
μ λ°λμ @keyframes
λ₯Ό μμ±ν΄μΌν¨μ μλ―Ένλ κ²μ΄λ€.
@keyframes
μμ μ¬μ©ν μ μλ properties λ λ€μκ³Ό κ°λ€.
from
: μμ μ€νμΌμ μ§μ .to
: μ’ λ£ μ€νμΌμ μ§μ .
μ°Έκ³ λ‘ μ¬κΈ°μ μμκ³Ό μ’ λ£λ μ λλ©μ΄μ μ΄ μ€νλλ μμ μ μμκ³Ό μ’ λ£λ₯Ό μλ―Ένλ€. μ λλ©μ΄μ μ΄ μμλκΈ° μ λκΈ° μνλ₯Ό μλ―Ένλ κ²μ΄ μλμ μ μνλλ‘ νλ€.
%
:from
,to
λ 0% μ 100% λ₯Ό μ¬μ©ν κ²κ³Ό κ°λ€. λ§μ½ λ λ€μν ꡬκ°μΌλ‘ λλκ³ μΆλ€λ©΄%
λ₯Ό μ¬μ©ν΄ μ¬λ¬ λ¨κ³λ³ μ λλ©μ΄μ μ§μ μ΄ κ°λ₯νλ€.
2. JavaScript - requestAnimationFrame
const element = document.getElementById("some-element-you-want-to-animate");
let start, previousTimeStamp;
let done = false;
function step(timeStamp) {
if (start === undefined) {
start = timeStamp;
}
const elapsed = timeStamp - start;
if (previousTimeStamp !== timeStamp) {
// Math.min() is used here to make sure the element stops at exactly 200px
const count = Math.min(0.1 * elapsed, 200);
element.style.transform = `translateX(${count}px)`;
if (count === 200) done = true;
}
if (elapsed < 2000) {
// Stop the animation after 2 seconds
previousTimeStamp = timeStamp;
if (!done) {
window.requestAnimationFrame(step);
}
}
}
window.requestAnimationFrame(step);
requestAnimationFrame
μ window
κ°μ²΄κ° κ°μ§κ³ μλ κΈλ‘λ² API λ€. μ΄ λ©μλμ νλ μμ 그릴 ν¨μλ₯Ό μ λ¬ν¨μΌλ‘μ¨
μλνλ λ°, λ¨ νλμ νλ μλ§ κ·Έλ¦¬κΈ° λλ¬Έμ μ λλ©μ΄μ
μ΄ μλνκΈ° μν΄μλ ν¨μ λ΄μμ λ€μ μμ μ requestAnimationFrame
μ
μ λ¬νλλ‘ ν΄ μ¬κ·ν¨μλ₯Ό μμ±νλ κ²κ³Ό μ μ¬νκ² μ½λλ₯Ό μμ±ν΄μΌνλ€.
CSS animation
λ μκ΄ μμ§λ§ JavaScript requestAnimationFrame
μ μ λλ©μ΄μ
μ μν΄ JavaScript μ½λ°± ν¨μλ₯Ό
μμ±ν΄μΌνλ€. κ·Έλ¦¬κ³ μ΄ μ½λ°±ν¨μκ° 16.6ms λ₯Ό μ΄κ³Όνλ λ¬΄κ±°μ΄ ν¨μμΌ κ²½μ° νλ μ μμ€μ΄
λ°μν μ μμμ μ μν΄μΌνλ€.
μ°Έκ³ λ‘ setInterval μ clearInterval
λ‘ λ°λ³΅μ μ€λ¨ν μ μλ―μ΄ requestAnimationFrame μ cancelAnimationFrame
λ‘ μ λλ©μ΄μ
μ€λ¨μ΄ κ°λ₯νλ€.
Timing Function μ μ μ©ν΄μΌ ν κ²½μ° CSS μ animation
μ μ¬μ©νκ±°λ,
JavaScript λ₯Ό μ¬μ©ν κ²½μ° GSAP
, Framer-motion
κ³Ό κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν΄μΌνμ§λ§ linear ν κΈ°λ³Έμ μΈ μ λλ©μ΄μ
μ
λ³λμ λΌμ΄λΈλ¬λ¦¬ μ€μΉ μμ΄ requestAnimationFrame
μ μ§μ ꡬνν΄ κ°λ³κ² μ¬μ©ν μ μλ€λ κ²μ΄ κ°μ₯ ν° μ₯μ μ΄λ€.
κ·Έλ¦¬κ³ μ μ½λλ₯Ό 보면 μ½λ°±ν¨μκ° λμ€νλ μ΄ μ£Όνμλ§νΌ μ€νλκΈ° λλ¬Έμ μΌν 보면 λ무 λ§μ μ€νμ΄ μμ¬ Stack Overflow
κ°
λ°μν κ² κ°μ§λ§ μμ νλ€λ κ²μ΄λ€! requestAnimationFrame
μ μ¬μ©λλ μ½λ°±ν¨μλ μ»΄νμΌλ¬μ μν 꼬리 μ¬κ·(Tail Recursion)
μ΅μ νμ λ§μ°¬κ°μ§λ‘ λΈλΌμ°μ κ° λμ€νλ μ΄ μ£Όμ¬μ¨κ³Ό λκΈ°ν λμ΄ λ°λ³΅λ¬Έμ μννλ―μ΄ μλνκΈ° λλ¬Έμ κ³Όλν μ½λ°± ν¨μλ‘ μΈν
Stack Overflow
λ¬Έμ λ₯Ό μΌμΌν€μ§ μλλ€.
κ·Έλ¦¬κ³ requeatAnimationFrame
μ animation
κ³Ό κ°μ₯ μ€μν μ°¨μ΄μ μ΄ νλ μ‘΄μ¬νλ€. λ€λ₯Έ JavaScript μ μν΄
λ©μΈμ€λ λκ° λΈλ‘νΉ λλλΌλ μ λλ©μ΄μ
μ΄ λ¦¬λ λλ§λ§ λμ§ μμ λΏ λμ€νλ μ΄ μ£Όμ¬μ¨κ³Ό λκΈ°ν λμ΄ κ³μ μ€νλλκ²μ λμΌνλ€.
νμ§λ§ ν΄λΉ νμ΄ ν¬κ·ΈλΌμ΄λμ μμ§ μμ κ²½μ°μλ κ³μ μ¬μλλ animation
κ³Ό λ¬λ¦¬ requeatAnimationFrame
μ
μ λλ©μ΄μ
μ μΌμ μ μ§νλ€. μ΄κ²μ λͺ¨λ°μΌ κΈ°κΈ°μ κ°μ νκ²½μμ 리μμ€λ₯Ό μ μ½ν μ μλ€.
Β | setInterval | animation | requestAnimationFrame |
---|---|---|---|
λμ€νλ μ΄ μ£Όμ¬μ¨ λκΈ°ν | X | O | O |
νλμ¨μ΄ κ°μ | X | O | O |
λ©μΈμ€λ λ λΈλ‘νΉ μ μ λλ©μ΄μ μ¬μ | λΈλ‘νΉ ν΄μ ν μ΄μ΄μ μ§ν | 리λ λλ§μ μ λμ§λ§ μ λλ©μ΄μ μ λΈλ‘νΉ μμ΄ μ§ν | 리λ λλ§μ μ λμ§λ§ μ λλ©μ΄μ μ λΈλ‘νΉ μμ΄ μ§ν |
νμ΄ ν¬κ·ΈλΌμ΄λκ° μλ λ μ λλ©μ΄μ μ¬μ | μ¬μ | μ¬μ | μΌμμ μ§ |
4. Examples - Progress Bar π©βπ»
1. setInterval
import { $ } from '/assets/js/utils/render.js';
import { delay } from '/assets/js/utils/fp.js';
const HERTZ = 60;
const TWO_SECONDS = 2;
const FRAME_INTERVAL = 1000 / HERTZ; // 16.6ms
const progress = $('.progress');
let runner;
const start = () => {
const changeQuantity = 100;
const totalFrames = HERTZ * TWO_SECONDS;
const oneFrameChangeQuantity = changeQuantity / totalFrames;
let frame = 0;
runner = setInterval(() => {
progress.style.width = `${oneFrameChangeQuantity * ++frame}%`;
if (frame === totalFrames) stop();
}, FRAME_INTERVAL);
};
const stop = () => {
clearInterval(runner);
attachStartEvent();
};
const task = async (event) => {
event.target.style.backgroundColor = '#a9a9a9';
let sum = 0;
await delay(10);
while (true) {
sum += 1;
if (sum > 3_000_000_000) break;
}
event.target.style.backgroundColor = '#ffe4c4';
};
const attachStartEvent = () => {
$('#start').addEventListener('click', start, { once: true });
};
attachStartEvent();
$('#stop').addEventListener('click', stop);
$('#task').addEventListener('click', task);
μμ μ¬μ©λ $
λ jQuery κ° μλκ³ querySelectorAll
, querySelector
λ₯Ό νλλ‘ ν©μ³ λ§λ ν¨μλ‘
render.js μμ νμΈν μ μλ€.
βμ€νβμ λλ¬λ³΄λ©΄ λ§λκΈ°κ° ν λ²μ© νλ μμ΄ μ΄κΈλ λΆλλ½μ§ μκ² μ¬λΌκ°λ κ²μ λ³Ό μ μλ€. λν λ§λκΈ°κ° μ¦κ°νλ λμ€ βλ¬΄κ±°μ΄ μμ βμ λλ₯΄λ©΄ μΌμμ μ§ λμλ€ λ¬΄κ±°μ΄ μμ μ΄ μ’ λ£λκ³ μ€νμ΄ λΉκ² λλ©΄ λ€μ λλ¨Έμ§ μ λλ©μ΄μ μ΄ μ§νλλ€.
2. animation
.progress {
height: 100%;
width: 0;
background-color: #31e51f;
animation-name: makeFull;
animation-duration: 2s;
animation-timing-function: ease;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: forwards;
animation-play-state: paused;
}
@keyframes makeFull {
from {
width: 0;
}
to {
width: 100%;
}
}
μ°μ κ°μ₯ λμ λλ κ²μ νλ μ μμ€ μμ΄ λ§€μ° λΆλλ¬μ΄ μ λλ©μ΄μ μ΄ μ μ©λλ€λ κ²μ΄λ€. βμ€λ¨βμ λλ λ€ βμ€νβμ λλ¬λ³΄λ©΄ μ λλ©μ΄μ μ΄ μΌμμ μ§ λμλ€ μ¬κ°λλ κ²μ λ³Ό μ μλ€.
κ·Έλ¦¬κ³ βλ¬΄κ±°μ΄ μμ
βμ λλ₯΄λ©΄ μ λλ©μ΄μ
μ΄ μ μ§λλ€. λΈλΌμ°μ μ 리λ λλ§(Reflow, Repaint)μ΄ λ©μΈμ€λ λμμ μ μ§λκΈ° λλ¬Έμ΄λ€.
νμ§λ§ μ setInterval κ³Όλ μ€μν μ°¨μ΄μ μ΄ μλλ°, λΈλΌμ°μ μ 리λ λλ§λ§ μ μ§λ λΏ CSS
μ λλ©μ΄μ
μ μ€λ¨λμ§ μλλ€λ κ²μ΄λ€.
κ·Έλ κΈ° λλ¬Έμ λ¬΄κ±°μ΄ μμ
μ΄ μ’
λ£λκ³ λ¦¬λ λλ§μ΄ λ λ CSS μ λλ©μ΄μ
μ λ
립μ μΌλ‘ μ²λ¦¬νλ νλ μμμ 리λ λλ§μ΄ μμλλ€.
μ¦, μκ°μ μΌλ‘ 리λ λλ§μ λμ§ μμ§λ§ μ λλ©μ΄μ νλ μ μ체λ JavaScript μ μ ν μν₯μ λ°μ§ μλλ€.
3. requestAnimationFrame
μ¬κΈ°μ μ¬μ©λ Animation μ requestAnimationFrame
μ μ¬μ©νκΈ° μ½λλ‘ Class λ₯Ό λ§λ κ²μΌλ‘ μ½λλ
Animation.js μμ νμΈν μ μλ€.
import { $ } from '/assets/js/utils/render.js';
import { delay } from '/assets/js/utils/fp.js';
import Animation from '/assets/js/utils/Animation.js';
const TWO_SECONDS = 2;
const animation = new Animation($('.progress'));
const start = async () => {
await animation.from({ width: '0%' }).to({ width: '100%' }).run(TWO_SECONDS);
attachStartEvent();
};
const stop = () => {
animation.stop();
attachStartEvent();
};
const task = async (event) => {
event.target.style.backgroundColor = '#a9a9a9';
let sum = 0;
await delay(10);
while (true) {
sum += 1;
if (sum > 3_000_000_000) break;
}
event.target.style.backgroundColor = '#ffe4c4';
};
const attachStartEvent = () => {
$('#start').addEventListener('click', start, { once: true });
};
attachStartEvent();
$('#stop').addEventListener('click', stop);
$('#task').addEventListener('click', task);
animation
κ³Ό λ§μ°¬κ°λ‘ νλ μ μμ€ μμ΄ λ§€μ° λΆλλ¬μ΄ μ λλ©μ΄μ
μ΄ μ μ©λλ κ²μ νμΈν μ μλ€.
κ·Έλ¦¬κ³ βλ¬΄κ±°μ΄ μμ βμ λλ¬λ³΄λ©΄ animation κ³Ό λ§μ°¬κ°μ§λ‘ λΈλΌμ°μ μ 리λ λλ§λ§ μ μ§λ λΏ CSS μ λλ©μ΄μ μ μ€λ¨λμ§ μλλ€λ κ²μ μ μ μλ€. μ λλ©μ΄μ μ΄ μ§νλλ λμ€ λ€λ₯Έ λ¬΄κ±°μ΄ μμ μ΄ λ©μΈμ€λ λλ₯Ό μ°¨μ§ν΄ 리λ λλ§ μμ²΄κ° λ©μΆλ κ²μ λ§μ μ μμ§λ§ μ λλ©μ΄μ μ체λ μ νν μ λλ©μ΄μ μ€νμ 보μ₯λ°μ μ μμμ μλ―Ένλ€.
λ¨, μ£Όμν΄μΌ ν κ²μ μμμλ λ§νλ―μ΄ requestAnimationFrame
μ μ½λ°± ν¨μκ° 16.6ms λ₯Ό μ΄κ³Όν΄ λμκ°λ©΄ μ λλ€.
μ λλ©μ΄μ
μ€νμ 보μ₯ν μ μμ§λ§ λμ€νλ μ΄ μ£Όμ¬μ¨μ 리λ λλ§μ ν μ μμ΄ μΌλΆ νλ μμ΄ μμ€λλ©° κ·Έλ €μ§κΈ° λλ¬Έμ΄λ€.
5. Examples - Animation Composition π©βπ»
1. setInterval
μ΄λ²μλ μ λλ©μ΄μ μ μ°κ²°ν΄λ³΄μ.
import { $ } from './render.js';
import { delay } from '/assets/js/utils/fp.js';
const HERTZ = 60;
const TWO_SECONDS = 2;
const FRAME_INTERVAL = 1000 / HERTZ; // 16.6ms
const box = $('.board .box');
const start = async () => {
const changeQuantity = 300;
const totalFrames = HERTZ * TWO_SECONDS;
const oneFrameChangeQuantity = changeQuantity / totalFrames;
let frame;
const toRight = () => {
frame = 0;
runner = setInterval(() => {
box.style.left = `${oneFrameChangeQuantity * ++frame}px`;
if (frame === totalFrames) clearInterval(runner);
}, FRAME_INTERVAL);
};
const toBottom = () => {
frame = 0;
runner = setInterval(() => {
box.style.top = `${oneFrameChangeQuantity * ++frame}px`;
if (frame === totalFrames) clearInterval(runner);
}, FRAME_INTERVAL);
};
const toLeft = () => {
frame = 0;
runner = setInterval(() => {
box.style.left = `${oneFrameChangeQuantity * (totalFrames - ++frame)}px`;
if (frame === totalFrames) clearInterval(runner);
}, FRAME_INTERVAL);
};
const toTop = () => {
frame = 0;
runner = setInterval(() => {
box.style.top = `${oneFrameChangeQuantity * (totalFrames - ++frame)}px`;
if (frame === totalFrames) clearInterval(runner);
}, FRAME_INTERVAL);
};
toRight();
await delay(TWO_SECONDS * 1000);
toBottom();
await delay(TWO_SECONDS * 1000);
toLeft();
await delay(TWO_SECONDS * 1000);
toTop();
await delay(TWO_SECONDS * 1000);
toBottom();
await delay(TWO_SECONDS * 1000);
toRight();
await delay(TWO_SECONDS * 1000);
toTop();
await delay(TWO_SECONDS * 1000);
toLeft();
await delay(TWO_SECONDS * 1000);
start();
};
start();
λΆκ°λ₯ν건 μλμ§λ§ νλ μλ μ νλ λΏ μλλΌ μ½λ μμ± λ° μ μ΄κ° νλ€λ€. μ¬μ§μ΄ μ΄λ°μμΌλ‘ μ½λλ₯Ό μμ±ν κ²½μ° νμ΄ ν¬κ·ΈλΌμ΄λλ₯Ό μ μ§νμ§ μμΌλ©΄ μ¬κ°ν μλ¬κ° λ°μνκΈ°λ νλ€.
2. animation
.box {
width: 100px;
height: 100px;
background-color: #31e51f;
position: absolute;
margin: 0;
padding: 0;
box-sizing: border-box;
animation-name: rotation;
animation-duration: 8s;
animation-timing-function: ease;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-play-state: running;
}
@keyframes rotation {
0% {
top: 0;
left: 0;
background-color: #31e51f;
}
25% {
top: 0;
left: 300px;
background-color: #e5401f;
}
50% {
top: 300px;
left: 300px;
background-color: #3662dc;
}
75% {
top: 300px;
left: 0;
background-color: #e5d81f;
}
100% {
top: 0;
left: 0;
background-color: #31e51f;
}
}
3. requestAnimationFrame
import { $ } from '/assets/js/utils/render.js';
import { pipe } from '/assets/js/utils/fp.js';
import ColorAddon from '/assets/js/utils/ColorAddon.js';
import Animation from '/assets/js/utils/Animation.js';
const animation = new Animation($('.board .box'), { ColorAddon });
const topLeft = {
top: '0px',
left: '0px',
backgroundColor: '#31e51f',
};
const topRight = {
top: '0px',
left: '300px',
backgroundColor: '#e5401f',
};
const bottomRight = {
top: '300px',
left: '300px',
backgroundColor: '#3662dc',
};
const bottomLeft = {
top: '300px',
left: '0px',
backgroundColor: '#e5d81f',
};
const toRight = (self) => self.to(topRight).run(2);
const toBottom = (self) => self.to(bottomRight).run(2);
const toLeft = (self) => self.to(bottomLeft).run(2);
const toTop = (self) => self.to(topLeft).run(2);
const toBottomReversed = (self) => self.to(bottomLeft).run(2);
const toRightReversed = (self) => self.to(bottomRight).run(2);
const toTopReversed = (self) => self.to(topRight).run(2);
const toLeftReversed = (self) => self.to(topLeft).run(2);
const rotation = pipe(
toRight,
toBottom,
toLeft,
toTop,
toBottomReversed,
toRightReversed,
toTopReversed,
toLeftReversed
);
const run = async () => {
await rotation(animation);
run();
};
run();
Reference
- βWindow: requestAnimationFrame() method.β MDN Web Docs. Jan. 19, 2024, accessed Apr. 24, 2024, MDN - rAF.