Performance & Optimization
requestAnimationFrame for Smooth Animations
Master requestAnimationFrame in JavaScript. Learn how requestAnimationFrame aligns animation calculations with browser paint cycles to prevent stuttering.
1. Introduction
requestAnimationFrame (rAF) is a browser API used to schedule animation calculations. It tells the browser that you want to perform an animation and requests that the browser call a specified callback function to update the animation before the next repaint cycle.
2. Why It Matters
Using timers like setTimeout or setInterval to trigger animations can result in stuttering (known as layout thrashing or jitter). Timers run independently of the screen refresh rate, which can lead to animation frames being skipped or calculated multiple times per paint cycle. requestAnimationFrame matches the screen's refresh rate (typically 60Hz or 120Hz) automatically, ensuring smooth animations.
3. Real-World Analogy
Think of a Flipbook Animator:
- setTimeout (Asynchronous animator): The animator draws a page and attempts to slide it into the book every 16ms, regardless of whether you are actively turning pages. Sometimes they slide in two drawings at once (skipped frame), and sometimes they slide in drawings when the book is closed (unnecessary work).
- requestAnimationFrame (Synced coordinator): The animator waits next to you. Every time you are about to turn a page (browser paint cycle), the animator hands you the next drawing just in time. The drawings are perfectly synchronized with your pages, and if you close the book (tab goes inactive), the animator pauses drawing, saving energy.
4. requestAnimationFrame vs setTimeout
Let's contrast using setTimeout and requestAnimationFrame to animate DOM elements:
5. High-Precision Timestamps
The callback function passed to requestAnimationFrame automatically receives a high-precision timestamp (representing milliseconds elapsed since document creation). You can use this timestamp to calculate time-based delta movements, ensuring the animation runs at the same speed regardless of the frame rate:
6. Practical Example
This script demonstrates creating a cancellable scroll-to-top animation using requestAnimationFrame and cancelAnimationFrame:
7. Common Mistakes
- Relying on timers for high-performance animations:
setTimeoutandsetIntervaldo not align with browser paint cycles, resulting in layout jitter and battery drain on background tabs. Always userequestAnimationFramefor DOM animations.
8. Quick Quiz
Q1: What happens to requestAnimationFrame loops when the user switches to a different browser tab?
A) The animation runs faster in the background to catch up
B) The browser pauses requestAnimationFrame executions automatically, saving CPU cycles and battery
Answer: B — The browser automatically pauses requestAnimationFrame when the tab goes inactive, improving performance and battery life.
9. Scenario-Based Challenge
The Smooth Custom Progress Bar Animator:
A dashboard page renders a loading progress bar from 0% to 100%. To ensure smooth rendering across high-refresh-rate screens (e.g. 144Hz monitors), write an animation loop using requestAnimationFrame that increments the progress width based on elapsed time.
10. Debugging Exercise
Explain why this custom animation runs at different speeds on different screens, and how to fix it:
let offset = 0;function draw() { // Bug: incrementing offset by a fixed pixel amount per frame! offset += 2; box.style.transform = `translateX(${offset}px)`;
if (offset < 200) { requestAnimationFrame(draw); } } requestAnimationFrame(draw);
View Solution
Diagnosis: The offset is incremented by a fixed pixel amount per frame. On a standard 60Hz monitor, the function runs 60 times per second (moving 120px/sec). On a 144Hz monitor, it runs 144 times per second (moving 288px/sec), causing the animation to run twice as fast on high-refresh-rate screens.
Fix: Calculate increments using a time-based delta value, utilizing the high-precision timestamp argument passed to the callback:
let start = null; const speed = 0.1; // 0.1px per millisecondfunction draw(timestamp) { if (!start) start = timestamp; const elapsed = timestamp - start;
// Calculate offset based on elapsed time, not frame rate! const offset = elapsed * speed; box.style.transform = `translateX(${Math.min(offset, 200)}px)`;
if (offset < 200) { requestAnimationFrame(draw); } } requestAnimationFrame(draw);
11. Interview Questions
🟢 Q1: What are the main advantages of requestAnimationFrame over setTimeout for UI animations?
Answer:
• Frame Synchronization: requestAnimationFrame is synchronized with the browser's repaint cycle, ensuring that calculations run immediately before the screen repaints.
• Tab Throttling: The browser automatically pauses requestAnimationFrame loops when the tab goes inactive, saving CPU cycles and battery life.
• High-Precision Timestamps: The callback receives a high-precision timestamp, allowing you to calculate time-based delta movements.
12. Production Considerations
- • CSS over JS: For simple transitions (like hover effects or opacity fades), prefer using CSS transitions or animations instead of JavaScript. CSS animations run on the browser's compositor thread, leaving the main thread free for JavaScript execution.