requestAnimationFrame vs setTimeout: When to Use Each

When building smooth animations or scheduling tasks in JavaScript, choosing between requestAnimationFrame
and setTimeout
can significantly impact your application’s performance. While both methods schedule function execution, they serve fundamentally different purposes and operate on different timing mechanisms.
Key Takeaways
requestAnimationFrame
synchronizes with the browser’s display refresh rate for smooth visual updatessetTimeout
provides general-purpose timing for non-visual tasks and background operations- Using the wrong method can cause performance issues, battery drain, and poor user experience
requestAnimationFrame
automatically pauses in inactive tabs, whilesetTimeout
continues running
Understanding the Core Differences
setTimeout: The General-Purpose Timer
setTimeout
executes a function after a specified delay in milliseconds. It’s a simple, predictable timer that works independently of the browser’s rendering cycle.
// Execute after 1 second
setTimeout(() => {
console.log('One second has passed');
}, 1000);
// With parameters
setTimeout((message) => {
console.log(message);
}, 2000, 'Hello after 2 seconds');
The key characteristic of setTimeout
is its task-queue based execution. When the delay expires, the callback joins the JavaScript event loop’s task queue, competing with other tasks for execution time.
requestAnimationFrame: The Animation Specialist
requestAnimationFrame
(rAF) synchronizes with the browser’s repaint cycle, typically running at 60 frames per second on most displays. It’s specifically designed for visual updates.
function animate(timestamp) {
// Update animation based on timestamp
const element = document.getElementById('animated-element');
element.style.transform = `translateX(${timestamp / 10}px)`;
// Continue animation
if (timestamp < 5000) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
Performance and Timing Considerations
Browser Rendering Pipeline Integration
The fundamental advantage of requestAnimationFrame
lies in its integration with the browser’s rendering pipeline. While setTimeout
fires whenever its delay expires, requestAnimationFrame
callbacks execute just before the browser calculates layout and paints pixels to the screen.
This synchronization eliminates common animation problems:
- Screen tearing: Visual artifacts from updates mid-frame
- Jank: Irregular frame timing causing stuttered motion
- Wasted renders: Drawing frames that never display
Resource Efficiency
requestAnimationFrame
automatically pauses when the browser tab becomes inactive, saving CPU cycles and battery life. setTimeout
continues executing in background tabs, though browsers may throttle it to once per second after about a minute.
// Battery-efficient animation loop
function gameLoop(timestamp) {
updatePhysics(timestamp);
renderGraphics();
requestAnimationFrame(gameLoop);
}
// Inefficient approach with setTimeout
function inefficientLoop() {
updatePhysics();
renderGraphics();
setTimeout(inefficientLoop, 16); // Attempting 60fps
}
Discover how at OpenReplay.com.
Practical Use Cases
When to Use requestAnimationFrame
Use requestAnimationFrame
for any visual updates:
- CSS property animations
- Canvas drawing operations
- WebGL rendering
- DOM position updates
- Progress indicators during animations
// Smooth scroll implementation
function smoothScrollTo(targetY, duration) {
const startY = window.scrollY;
const distance = targetY - startY;
const startTime = performance.now();
function scroll(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function for smoother motion
const easeInOutQuad = progress * (2 - progress);
window.scrollTo(0, startY + distance * easeInOutQuad);
if (progress < 1) {
requestAnimationFrame(scroll);
}
}
requestAnimationFrame(scroll);
}
When to Use setTimeout
Choose setTimeout
for non-visual tasks:
- Delayed API calls
- Debouncing user input
- Polling operations
- Scheduled background tasks
- One-time delays
// Debounced search
let searchTimeout;
function handleSearchInput(query) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
}
// Retry logic with exponential backoff
function fetchWithRetry(url, attempts = 3, delay = 1000) {
return fetch(url).catch(error => {
if (attempts > 1) {
return new Promise(resolve => {
setTimeout(() => {
resolve(fetchWithRetry(url, attempts - 1, delay * 2));
}, delay);
});
}
throw error;
});
}
Common Pitfalls and Solutions
Frame Rate Assumptions
Never assume a fixed frame rate with requestAnimationFrame
. Different displays refresh at different rates (60Hz, 120Hz, 144Hz). Always use the timestamp parameter for time-based animations:
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
const element = document.getElementById('moving-element');
const currentLeft = parseFloat(element.style.left) || 0;
// Move 100 pixels per second regardless of frame rate
const pixelsPerMs = 100 / 1000;
element.style.left = `${currentLeft + pixelsPerMs * deltaTime}px`;
requestAnimationFrame(animate);
}
Memory Leaks
Always store and clear animation frame IDs when components unmount:
let animationId;
function startAnimation() {
function animate() {
// Animation logic here
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
// Clean up on page unload
window.addEventListener('beforeunload', stopAnimation);
Quick Decision Guide
Use Case | Best Choice | Reason |
---|---|---|
Smooth animations | requestAnimationFrame | Syncs with display refresh |
Canvas/WebGL rendering | requestAnimationFrame | Prevents tearing and jank |
API polling | setTimeout | Not tied to visual updates |
User input debouncing | setTimeout | Needs precise delay control |
Progress bars during animation | requestAnimationFrame | Visual feedback requirement |
Background data processing | setTimeout | Continues when tab inactive |
Game loops | requestAnimationFrame | Optimal performance and battery life |
Conclusion
The choice between requestAnimationFrame
and setTimeout
isn’t about which is “better”—it’s about using the right tool for the job. For visual updates and animations, requestAnimationFrame
provides superior performance through browser synchronization. For general timing needs and background tasks, setTimeout
offers the flexibility and predictability you need. Understanding these differences ensures your JavaScript applications deliver smooth user experiences while efficiently managing system resources.
FAQs
While you can use setTimeout with a 16.67ms delay to approximate 60fps, it won't sync with the browser's actual refresh cycle. This leads to dropped frames, jank, and wasted CPU cycles. requestAnimationFrame automatically adapts to the display's refresh rate.
Yes, requestAnimationFrame adapts to the monitor's refresh rate. On a 120Hz display, it fires approximately 120 times per second. Always use the timestamp parameter to calculate delta time for consistent animation speed across different displays.
If your callback exceeds the frame budget, the browser will skip frames to maintain responsiveness. This causes visible stuttering. Consider optimizing your code, using web workers for heavy calculations, or reducing the animation complexity.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.