Back

requestAnimationFrame vs setTimeout: When to Use Each

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 updates
  • setTimeout 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, while setTimeout 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
}

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 CaseBest ChoiceReason
Smooth animationsrequestAnimationFrameSyncs with display refresh
Canvas/WebGL renderingrequestAnimationFramePrevents tearing and jank
API pollingsetTimeoutNot tied to visual updates
User input debouncingsetTimeoutNeeds precise delay control
Progress bars during animationrequestAnimationFrameVisual feedback requirement
Background data processingsetTimeoutContinues when tab inactive
Game loopsrequestAnimationFrameOptimal 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.

OpenReplay