Choosing the Right Animation Timing Mechanism: requestAnimationFrame vs. Intervals
A decision guide comparing requestAnimationFrame, setTimeout, and setInterval for JavaScript animations, with a time-based implementation example and frame-rate verification steps.
28 Jul 2026, 18:05 UTC

When building visual animations in JavaScript, the primary challenge is not moving an object, but doing so in sync with the browser's rendering cycle. Using standard timers to drive visual updates often leads to 'jank' (stuttering), unnecessary CPU usage, and battery drain when the user switches tabs.
The takeaway is simple: use requestAnimationFrame (rAF) for almost all visual animations, as it synchronizes with the display's refresh rate and pauses when the tab is inactive. Reserve setTimeout or setInterval for non-visual logic that must execute regardless of screen state.
Mechanism Comparison
| Option | Pros | Cons |
|---|---|---|
| requestAnimationFrame | Syncs to display refresh; energy efficient; auto-throttles in hidden tabs. | Requires recursive callback pattern; not available in legacy browsers (IE<10). |
| setTimeout | Simple syntax; works in all environments. | Drifts over time; runs even when tab is hidden; manual frame-rate math required. |
| setInterval | Repeats automatically without manual recursion. | Accumulates lag; hidden-tab issues; difficult to stop precisely. |
Trade-offs and Constraints
The most significant trade-off involves how the browser handles the execution budget. Most modern monitors refresh at 60Hz, meaning a frame is drawn every 16.67ms. If you use setTimeout(fn, 16), your code might execute slightly after the browser has started painting, causing a frame to be skipped or delayed until the next cycle, resulting in visible stuttering.
Conversely, requestAnimationFrame tells the browser you want to perform an animation and requests the next repaint. However, rAF has a constraint: it pauses when the user switches tabs or minimizes the window. If your application logic relies on continuous time (like a multiplayer game state update), you must calculate the elapsed time using performance.now() inside the callback rather than assuming each frame represents a fixed duration. Also avoid mixing rAF with setTimeout or setInterval for the same animation, as that can produce duplicate frames or inconsistent timing; pick one mechanism and clean up all associated IDs.
Implementation: Smooth Linear Animation
To implement a smooth animation, track the delta between frames. This ensures the animation moves at a consistent speed regardless of whether the user has a 60Hz or 144Hz monitor.
const box = document.querySelector('.box');
let startTime = null;
let animationId = null;
const duration = 2000; // animation lasts 2000ms
function step(timestamp) {
// timestamp is a high-resolution DOMTimeStamp provided by rAF
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime; // elapsed time in ms
// Calculate position based on time, not frame count
const position = Math.min(progress, 50);
box.style.transform = `translateX(${position}px)`;
if (progress < duration) {
// Request the next frame
animationId = requestAnimationFrame(step);
}
}
// Start the animation
animationId = requestAnimationFrame(step);
// To stop the animation later:
function stopAnimation() {
cancelAnimationFrame(animationId);
}
This code runs in the browser (any modern version; rAF has been supported since IE10). No special permissions are required. The main risk is forgetting to call cancelAnimationFrame when the animation should stop early, which leaves the callback loop running and consuming resources.
Verifying Frame Rate Consistency
To verify that your animation is correctly synchronized and behaving efficiently, follow these diagnostic steps in your browser console:
- Delta Check: Modify your
stepfunction to logconsole.log(timestamp - lastTimestamp). On a 60Hz screen, the value should hover around 16.6ms (±2-3ms). - Throttling Test: While the animation is running, switch to a different tab. You should observe the console logs stop, confirming that the browser is throttling the execution to save resources.
- Cleanup Validation: Call
cancelAnimationFrame(animationId)and verify that no further logs appear, ensuring no memory leaks or background processes are running.
Limitations
Because rAF pauses in hidden tabs, any logic that assumes continuous time will break. Compensate by deriving all state from performance.now() deltas, or switch to setTimeout for work that must continue while the page is hidden. Frame deltas will also vary on high-refresh-rate displays (e.g., ~8.3ms at 120Hz), so never hardcode 16.67ms into movement calculations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.