requestAnimationFrame vs. setTimeout: Why Frame Sync Matters More Than You Think
Prerequisites: Essential Knowledge for Understanding requestAnimationFrame
Before learning requestAnimationFrame, you need to establish two foundational understandings: the nature of animation smoothness and the flaws of traditional approaches.
1. Monitor Refresh Rate and the Browser's "Frame"
- Refresh Rate: The number of times a monitor updates the screen per second, measured in Hertz (Hz). Mainstream monitors are 60Hz, meaning the screen refreshes 60 times per second; high-refresh-rate screens can reach 120Hz or 144Hz. The interval between two refreshes is called a frame. At 60Hz, the time budget for a single frame is approximately 16.67 milliseconds.
- A Browser Frame: The complete process by which the browser produces a full screen update, including JavaScript execution, style calculation, layout, painting, and compositing, ultimately outputting to the display. The core of smooth animation is completing all work within each frame on time, without exceeding the budget or dropping frames.
2. The Core Pain Points of Traditional Timer-Based Animation
Early front-end animations were commonly implemented by periodically modifying element styles using setTimeout or setInterval, which have two inherent flaws:
- Timing Desynchronization: Timers are macro-tasks in the event loop (asynchronous tasks in the browser's task queue, executed in order). Their execution timing is unrelated to the browser's rendering cycle. A callback can land at any moment within a frame, easily leading to dropped frames and stuttering.
- Unrestrained Resource Usage: Even when a page is switched to the background or a tab is not visible, timers continue to execute, wasting CPU resources and device battery.
requestAnimationFrame is a native API created precisely to solve the timing synchronization and performance optimization problems of animation.
1. Core Concepts of requestAnimationFrame
1. Definition
requestAnimationFrame (often abbreviated as rAF in the industry) is a browser-native API defined by the HTML5 standard. It is used to register a callback function with the browser, requesting the browser to execute that callback before the next screen repaint. It is a scheduling interface specifically optimized for visual animations. A simple explanation: it's like sending the browser a "pre-render execution" appointment, naturally aligning animation code with the screen's refresh rhythm and mechanically preventing stuttering caused by desynchronization.
2. Underlying Principles
The core essence of rAF is a callback scheduling mechanism deeply bound to the browser's rendering pipeline. The core logic has three points:
- Automatic Refresh Rate Alignment: The browser automatically schedules the callback execution frequency based on the current display's refresh rate, ensuring the callback executes at most once per frame, with no redundant calculations.
- Precise Execution Timing: The callback is fixed to execute at the point in each frame 'after JS task execution finishes and before style calculation begins'. Modifying element styles at this time will be processed for layout and painting in the current frame, avoiding cross-frame delays.
- Automatic Background Suspension: When a page tab is hidden or minimized, the browser automatically pauses the scheduling of rAF callbacks; it automatically resumes when the page becomes visible again, thus reducing performance overhead.
3. Concrete Example
Below is the standard implementation for moving an element horizontally at a constant speed using rAF, which is also the industry's common best practice:
const target = document.querySelector('.animate-box');
const totalDistance = 300; // Total movement distance 300px
const totalDuration = 2000; // Total animation duration 2000ms
let startTimestamp = null; // Animation start timestamp
// Animation function executed per frame
function step(currentTimestamp) {
// Record start time on first execution
if (!startTimestamp) startTimestamp = currentTimestamp;
// Calculate elapsed time and animation progress
const elapsed = currentTimestamp - startTimestamp;
const progress = Math.min(elapsed / totalDuration, 1); // Progress clamped to 0~1
// Update element position
target.style.transform = `translateX(${progress * totalDistance}px)`;
// If progress is not complete, register the next frame callback
if (progress < 1) {
requestAnimationFrame(step);
}
}
// Start the animation
requestAnimationFrame(step);
The core characteristic of this approach is calculating animation progress based on time difference, rather than moving a fixed number of pixels per frame. Even if the device frame rate fluctuates, the total animation duration remains consistent, avoiding the problem of animations slowing down on slower devices.
4. Application Scenarios
- Custom animations and transition effects for DOM elements, especially those requiring fine-grained progress control
- Graphics-drawing animations such as Canvas, SVG, and WebGL
- Page scroll-linked effects, parallax scrolling, smooth scrolling
- Chunked rendering of large batches of DOM elements, using idle time within each frame to update the page in batches
2. Execution Timing of requestAnimationFrame
1. Definition
Execution timing refers to the specific stage within a single frame's rendering pipeline where the rAF callback resides, and its order of execution relative to other asynchronous tasks.
2. Underlying Principles
The standard pipeline for the browser to complete one frame of a screen, in order of execution, consists of the following stages:
- Process user input events (clicks, scrolls, keyboard input, etc.)
- Execute macro-tasks and micro-tasks (i.e., tasks in the JavaScript event loop queue)
- Execute the requestAnimationFrame callback queue
- Style Calculation (Style): Calculate the final effective CSS styles for all elements
- Layout (Layout, commonly known as reflow): Calculate the position and size of elements
- Paint (Paint, commonly known as repaint): Draw the visual content of elements onto their respective layers
- Compositing (Composite): Merge multiple layers into the final screen image and output it to the display
The rAF callback is positioned exactly after 'regular JS tasks' and before 'style calculation'. Modifying styles at this moment will be directly processed by the current frame's layout and paint processes, making it the optimal time to modify visual styles.
3. Concrete Example
Comparing the timing difference in applying style changes between a timer and rAF:
// Method A: setTimeout to modify styles
setTimeout(() => {
target.style.width = '200px';
// This change will likely miss the current frame and needs to wait for the next frame to render on screen
}, 0);
// Method B: rAF to modify styles
requestAnimationFrame(() => {
target.style.width = '200px';
// This change will be calculated during the current frame's layout stage and can be rendered in the same frame
});
setTimeout(fn, 0) places the callback into the next macro-task queue, executing later than the current frame's rendering process; whereas the rAF callback executes before the current frame renders, so style changes can be immediately processed by this rendering pipeline.
4. Application Scenarios
- Synchronized animations requiring precise control over when style changes take effect
- Scenarios involving reading DOM dimensions and immediately modifying styles, to avoid forced synchronous layouts
- Coordinated updates for multiple elements or animations, ensuring all changes take effect within the same frame
3. Selection Comparison with Traditional Timer-Based Animation
1. Definition
setTimeout / setInterval are general-purpose timing task APIs designed for delayed task execution; requestAnimationFrame is a dedicated rendering-optimized API designed for smooth visual updates. Their underlying scheduling mechanisms are completely different, and their applicable scenarios have clear boundaries.
2. Underlying Principles
- Timer-Based Animation: Based on macro-task scheduling in the event loop, callbacks enter the task queue and wait for the main thread to become idle. When the main thread is busy, callbacks are postponed. Their execution timing is unrelated to the rendering cycle, easily leading to situations where a callback executes multiple times within one frame, or once across multiple frames, causing animation stuttering and frame skipping.
- rAF Animation: Directly attached to the rendering pipeline and uniformly scheduled by the browser. No matter how many callbacks are registered within a frame, they are all executed sequentially during the rAF stage, preventing callback accumulation. When the main thread is blocked, the frame rate automatically decreases, ensuring the continuity of screen updates.
3. Concrete Comparison
| Comparison Dimension | setTimeout / setInterval | requestAnimationFrame |
|---|---|---|
| Execution Timing | Scheduled by event loop, unrelated to rendering cycle | Bound to rendering cycle, fixed execution before each frame repaint |
| Frame Rate Stability | Unstable, prone to frame drops and skipping | Stable, automatically aligns with monitor refresh rate |
| Background Page Behavior | Continues execution, consumes system resources | Automatically pauses, resumes automatically when page returns |
| Time Precision | Millisecond level, has a minimum 4ms delay limitation | Microsecond-level high-precision timestamp, no extra delay |
| Callback Accumulation | High-frequency calls easily cause callback accumulation | Multiple calls within a single frame are merged and executed, no accumulation |
| Design Purpose | General-purpose asynchronous timing tasks | Specifically for visual animation and rendering optimization |
4. Application Scenarios
- Prefer rAF: For all visual animations and screen update logic, especially continuously running animations and smooth transition effects.
- Prefer Timers: For non-visual timing tasks, such as data polling, delayed business logic execution, and timing operations unrelated to rendering.
4. Core Features of requestAnimationFrame
1. Definition
rAF comes with three native features that are its core advantages as the premier choice for animation: automatic background suspension, a high-precision timestamp, and a cancellation mechanism.
2. Underlying Principles
- Automatic Background Suspension: The browser detects the tab state through the Page Visibility API. When the page is hidden, it stops scheduling rAF callbacks, reducing the frame rate to 0; when the page becomes visible again, it automatically resumes scheduling, drastically reducing the performance overhead of background pages.
- High-Precision Timestamp: The rAF callback automatically receives a parameter of type
DOMHighResTimeStamp, representing the precise time the callback was triggered. This time is measured from when the page started loading, has microsecond-level precision, and is unaffected by system time changes. It is the standard basis for calculating animation progress. - Cancellation Mechanism: Calling
requestAnimationFramereturns a non-zero integer ID. The corresponding pending callback can be canceled usingcancelAnimationFrame(id), a logic consistent withclearTimeout, used for actively terminating an animation.
3. Concrete Example
A standard encapsulation for starting and stopping an animation:
class AnimationController {
constructor() {
this.animationId = null;
}
start(animateFn) {
const step = (timestamp) => {
animateFn(timestamp);
this.animationId = requestAnimationFrame(step);
};
this.animationId = requestAnimationFrame(step);
}
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
}
Calling the stop() method in scenarios like component unmounting or modal closing can thoroughly clean up callbacks, preventing memory leaks and invalid executions.
4. Application Scenarios
- Long-running page animations, automatically optimizing background resource usage
- Animations requiring high timing precision, using timestamps for accurate progress control
- Interactive animation components that support pausing, resetting, and terminating at any time
5. Common Misconceptions
Misconception: requestAnimationFrame always executes every 16.6ms
Correction: The execution interval is determined by the monitor's refresh rate. A 60Hz screen is about 16.6ms, a 120Hz high-refresh screen is about 8.3ms; if the main thread is blocked by a long task, the frame interval will be further extended, and rAF will automatically postpone execution. It is not a fixed time interval.
Misconception: Using requestAnimationFrame guarantees smooth animation
Correction: rAF only solves the problem of execution timing synchronization. If the callback itself contains heavy computation or frequently triggers reflow and repaint, causing the total workload to exceed the single-frame time budget, the animation will still stutter. The core of smooth animation is controlling the total workload within a single frame, not simply using rAF.
Misconception: requestAnimationFrame can only be used for animations
Correction: The essence of rAF is 'execute before the next frame renders'. Besides animation, it can also be used for chunked rendering of large DOM batches, avoiding forced synchronous layouts, splitting long tasks, and other scenarios. Any logic that needs to align with the rendering rhythm can use it.
Misconception: requestAnimationFrame and setTimeout(fn, 0) have similar effects
Correction: Their execution timings are on completely different levels.
setTimeout(fn, 0)places the callback into the next macro-task queue, executing after the current frame renders; the rAF callback executes before the current frame renders, much earlier than setTimeout 0, and with higher precision.
6. Practical Implementation Advice
Basic Usage Standards
- Adopt the 'time-difference progress calculation' approach, not fixed increments. This avoids inconsistent animation speeds due to device frame rate differences and ensures a uniform total animation duration across different devices.
- When an animation stops or a component unmounts,
cancelAnimationFramemust be called to clean up callbacks, preventing memory leaks and invalid executions. - Use the high-precision timestamp passed into the callback for animation progress calculation, not
Date.now(), which has lower precision and is affected by system time changes.
Performance Optimization Advice
- Do not execute heavy, non-rendering computation logic inside the rAF callback. Time-consuming business logic should be placed in
requestIdleCallbackor a Web Worker whenever possible. - Avoid reading DOM styles and then writing DOM styles within a rAF callback, as this operation triggers a forced synchronous layout, leading to significant performance degradation.
- Multiple animation logics within the same frame should be merged into a single rAF callback to reduce the extra overhead of multiple scheduling calls.
Boundary Compatibility Advice
- Lower versions of IE do not support rAF. A polyfill simulating it with
setTimeoutcan be used for graceful degradation. - For non-high-priority animations, listen for the
visibilitychangeevent and actively pause the logic when the page is hidden to further optimize resource usage. - On high-refresh-rate screens, if an excessively high frame rate is not needed, frame throttling can be implemented using timestamps to control the animation at a target frame rate, reducing performance overhead.