EN/CH Mode
BRUCE_FE Frontend Interview Notes - User Interaction Optimization - RequestAnimationFrame to Prevent Animation Frame Drop
In-depth analysis of how RequestAnimationFrame API optimizes frontend animation performance, prevents frame drops, and its key differences from traditional timers. Includes practical examples and common interview questions.
EN/CH Mode

Lazy to read articles? Then watch videos!
Basic Concepts
RequestAnimationFrame (rAF) is a browser API for optimizing animation performance, allowing your animations to execute at the most appropriate time to ensure a smooth visual experience. Compared to traditional setTimeout and setInterval, rAF can effectively prevent animation frame drops.
Traditional Timer Animation:
May execute at any time → Not synchronized with browser render cycle → Visual stuttering, frame drops
RequestAnimationFrame:
Synchronized with browser render process → Executes before next repaint → Smooth animation, better performanceBrowser Rendering Process and Frame Drop Principles
The browser's rendering process typically executes about 60 times per second (60FPS). If each rendering cycle exceeds about 16.7ms (1000ms/60), frame drops will occur.
Browser rendering cycle during execution:
Each frame (about every 16.6ms)
→ Process JS task queue (micro/macro tasks)
→ Execute requestAnimationFrame callbacks ✅
→ Start layout (reflow) & paint (repaint)
→ Composite (display composition)
→ Next frame cycle
Ideal case (60FPS):
[Frame1] → [Frame2] → [Frame3] → [Frame4] → [Frame5]
16.7ms 16.7ms 16.7ms 16.7ms 16.7ms
Frame drop case:
[Frame1] → [Drop!] → [Frame3] → [Frame4] → [Frame5]
25ms X 16.7ms 16.7ms 16.7mssetTimeout vs requestAnimationFrame
| Feature | setTimeout | requestAnimationFrame |
|---|---|---|
| Execution Timing | After specified time (imprecise) | Before browser repaint (synced with display) |
| Background Tab | Continues running (wastes resources) | Auto-pauses (saves resources) |
| Animation Smoothness | Prone to frame drops | Reduces frame drops |
| Battery Consumption | Higher | Lower |
Basic Usage of requestAnimationFrame
// Basic usage
function animate() {
// Update animation state
element.style.left = `${position++}px`;
// Recursive call, create animation loop
requestAnimationFrame(animate);
}
// Start animation
requestAnimationFrame(animate);
// Cancel animation
const animationId = requestAnimationFrame(animate);
cancelAnimationFrame(animationId);Practical Application Case: Smooth Scroll
The following is an example of implementing smooth scrolling to the top of the page using requestAnimationFrame:
// Smooth scroll to page top
function scrollToTop() {
const currentPosition = window.pageYOffset;
if (currentPosition > 0) {
// Smooth scroll with 10% of current scroll distance
window.scrollTo(0, currentPosition - currentPosition / 10);
requestAnimationFrame(scrollToTop);
}
}
// Click button to start smooth scroll
document.querySelector('.scroll-top-button').addEventListener('click', function() {
requestAnimationFrame(scrollToTop);
});Advanced Application: Animation Performance Optimization Techniques
1. Avoid Layout Thrashing
Reducing forced reflow and repaint is key to improving animation performance, should separate read and write operations:
// Bad practice: Multiple elements alternating read/write, causing multiple forced reflows
function badAnimation() {
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
// Read DOM (forces browser to calculate latest layout)
const width = box.offsetWidth;
// Write DOM (changes layout)
box.style.width = `${width + 1}px`;
// Read again (forces layout recalculation again)
const height = box.offsetHeight;
// Write again (changes layout again)
box.style.height = `${height + 1}px`;
});
requestAnimationFrame(badAnimation);
}
// Good practice: First batch read all data, then batch write
function goodAnimation() {
const boxes = document.querySelectorAll('.box');
const measurements = [];
// Read phase - read all needed data at once
boxes.forEach(box => {
measurements.push({
width: box.offsetWidth,
height: box.offsetHeight,
element: box
});
});
// Write phase - complete all DOM modifications at once
measurements.forEach(data => {
data.element.style.width = `${data.width + 1}px`;
data.element.style.height = `${data.height + 1}px`;
});
requestAnimationFrame(goodAnimation);
}
/*
Layout thrashing visualization comparison:
Bad practice (multiple reflows):
┌─────────────────────────────────┐
│ Read element1 size → Reflow #1 │
│ Modify element1 size │
│ Read element1 height → Reflow #2│
│ Modify element1 height │
│ Read element2 size → Reflow #3 │
│ Modify element2 size │
│ Read element2 height → Reflow #4│
│ Modify element2 height │
│ ... │
└─────────────────────────────────┘
Result: Multiple forced reflows in one frame, performance degrades
Good practice (batch processing):
┌─────────────────────────────────┐
│ Read all element sizes → Reflow #1│
│ Modify all element sizes and heights│
└─────────────────────────────────┘
Result: Only one reflow in one frame, performance improves
*/2. Use CSS transform instead of modifying position
Prioritize using properties that affect the compositing layer instead of the layout:
// Bad practice: Modify left/top (triggers layout)
function inefficientMove() {
box.style.left = `${xPos}px`;
box.style.top = `${yPos}px`;
}
// Good practice: Use transform (only triggers compositing)
function efficientMove() {
box.style.transform = `translate(${xPos}px, ${yPos}px)`;
}Common Interview Questions and Answers
(1) What are the main differences between requestAnimationFrame and setTimeout/setInterval?
A: requestAnimationFrame synchronizes with the browser's rendering cycle, ensuring animations execute at optimal times; while setTimeout/setInterval use fixed time intervals, ignoring the browser's rendering process, which can cause frame drops. Additionally, rAF automatically pauses in invisible tabs to save resources and adapts to displays with different refresh rates.
(2) Why is requestAnimationFrame more suitable for implementing animations than setTimeout?
A: rAF can reduce frame drops, lower energy consumption, and synchronize with browser repaints; while setTimeout's timing is imprecise, potentially causing multiple repaints in one cycle or completely missing repaint opportunities.
(3) How to implement animation throttling using requestAnimationFrame?
// Use flag variable to control if there's a pending animation frame
let ticking = false;
function onScroll(e) {
// Check if there's already a scheduled animation frame
if (!ticking) {
// Use rAF to ensure handler executes before next repaint
// instead of executing on every scroll event (could trigger multiple times in one frame)
requestAnimationFrame(() => {
doSomethingWithScrollPosition(); // Actual scroll position handling logic
ticking = false; // Reset flag, allow next frame processing
});
ticking = true; // Mark that there's a pending animation frame
}
// If ticking is true, ignore this scroll event
// until the previous rAF completes
}
// Listen for scroll events
window.addEventListener('scroll', onScroll);
/*
┌─────────────────────────────────────────────────┐
│ Without throttling: │
│ Scroll → Handle → Scroll → Handle → Scroll → Handle │
│ (Could trigger 10+ times in one frame, causing performance issues) │
│ │
│ With rAF throttling: │
│ Scroll → Scroll → Scroll → rAF(Handle once) → ... │
│ └─── One render cycle/frame ─────┘ │
└─────────────────────────────────────────────────┘
*/(4) How does requestAnimationFrame perform on displays with different refresh rates?
A: rAF automatically adapts to the display's refresh rate. It executes approximately every 16.7ms on 60Hz displays and every 8.3ms on 120Hz displays, ensuring optimal animation performance across different devices.
(5) What is the relationship between requestAnimationFrame and the Event Loop?
A: requestAnimationFrame has a special execution timing in the event loop. It is called after all microtasks of a frame have completed and before rendering, specifically in this order:
1. Execute synchronous code
2. Execute microtasks (Promise, MutationObserver, etc.)
3. Execute requestAnimationFrame callbacks
4. Execute rendering (Layout, Paint, Composite)
5. Execute macrotasks (setTimeout, setInterval, etc.)
6. Repeat steps 1-5