Crafting Performant Animations: requestAnimationFrame and Traditional Timing Functions ExploredAn In-depth Analysis of Timing Methods for Animations and their Impact on User Experience

Introduction: Why Animation Performance Is Still a Problem in 2026

Animations on the web are deceptively hard. On paper, it looks simple: change a value over time, redraw something, repeat. In reality, animation performance is tightly coupled to how browsers schedule work, how often screens refresh, and how much JavaScript you force onto the main thread. Many developers still reach for setTimeout or setInterval out of habit, not because they're the right tool. The result is stuttering animations, unnecessary CPU usage, battery drain, and users who subconsciously feel that something is “off” about your product, even if they can't explain why.

What makes this worse is that the web platform has offered a better alternative for over a decade: requestAnimationFrame (rAF). It is well-documented on MDN and standardized in the HTML Living Standard maintained by WHATWG. Yet misuse and misunderstanding are everywhere. rAF is often treated as a magical performance fix, when in reality it is just a scheduling primitive that aligns your animation logic with the browser's rendering pipeline. Used correctly, it enables smooth, predictable animations. Used poorly, it can be just as bad as timers.

This article is brutally honest by design. There is no nostalgia for “the old days” of timers, and no hype-driven exaggeration about rAF being a silver bullet. We will dissect how timing functions actually work, how browsers render frames, and where each approach breaks down. The goal is not academic purity, but practical clarity you can apply to real production systems.

How Browsers Actually Render Frames (And Why Timing Matters)

Before comparing APIs, you need to understand the rendering loop. Browsers render content in discrete frames, typically synchronized to the display's refresh rate. On most devices, that's 60Hz, meaning one frame roughly every 16.67ms. Higher refresh rate displays exist, but the core idea stays the same. Each frame involves style calculations, layout, paint, and compositing. JavaScript execution happens on the main thread and competes directly with these steps.

When you use setTimeout or setInterval, you are asking the browser to run a callback after at least a given delay. That word “at least” matters. The HTML specification explicitly states that timer callbacks are clamped and delayed when the main thread is busy or when tabs are throttled in the background. This behavior is documented by both WHATWG and MDN. In practice, this means your animation timing drifts, frames are skipped, and updates can happen in the middle of a rendering cycle, forcing extra work.

requestAnimationFrame, by contrast, queues your callback to run before the next repaint. The browser decides the optimal moment, based on the current refresh rate and rendering workload. This is not guesswork; it is designed to align JavaScript-driven visual updates with the rendering pipeline. The MDN documentation is explicit: rAF callbacks fire before the browser performs the next repaint, making them ideal for visual updates.

The key insight is that animation performance is not about time alone. It is about coordination. Timers operate on wall-clock delays. rAF operates on frames. If you animate visuals, frames are what matter.

Traditional Timing Functions: setTimeout and setInterval Under the Microscope

It's tempting to dismiss setTimeout and setInterval as “bad,” but that's lazy thinking. These APIs are not broken; they are just frequently misused. They were designed for delayed and repeated execution, not for frame-perfect visual updates. The problem arises when developers force them into animation roles they were never meant to play.

Consider this classic example:

let position = 0;

setInterval(() => {
  position += 5;
  element.style.transform = `translateX(${position}px)`;
}, 16);

On the surface, this looks reasonable. Sixteen milliseconds approximates 60fps, right? In reality, this code is fragile. If the main thread is busy, callbacks pile up or get delayed. The browser may execute multiple callbacks back-to-back, or skip frames entirely. Worse, the callback might fire just after a repaint, forcing the browser to wait until the next frame to show your update, adding latency and visible stutter.

Another harsh truth: timers do not automatically pause when a tab is hidden, but browsers heavily throttle them. Modern browsers clamp timer resolution in background tabs to improve battery life and reduce CPU usage, as documented by MDN and Chrome's performance guidelines. Your animation logic keeps “running,” but frames are not being rendered. When the tab becomes visible again, state jumps abruptly.

Timers still have legitimate use cases. Polling, debouncing, timeouts, and non-visual delays are exactly what they're good at. The mistake is treating them as animation clocks. That mistake costs performance, energy efficiency, and user trust.

requestAnimationFrame: What It Solves—and What It Doesn't

requestAnimationFrame exists to solve a very specific problem: synchronizing JavaScript-driven visual updates with the browser's rendering cycle. When you call rAF, you are effectively saying, “Run this code right before you repaint, when it makes sense to update visuals.” That alignment alone eliminates an entire class of timing bugs.

Here is the same animation rewritten using rAF:

let position = 0;

function animate() {
  position += 5;
  element.style.transform = `translateX(${position}px)`;
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

This code is not magically faster, but it is predictable. If the browser can render at 60fps, your animation updates at 60fps. If the device is under load and can only manage 30fps, your animation gracefully degrades without piling up callbacks. When the tab is hidden, most browsers pause rAF entirely, resuming when the tab becomes visible. This behavior is documented on MDN and is critical for power efficiency.

However, rAF is not a free pass. Heavy computation inside an rAF callback will still block rendering. If you do layout thrashing, expensive DOM queries, or synchronous JSON parsing in your animation loop, rAF will faithfully schedule that work—and your frames will still drop. The API gives you the right time to run code, not a guarantee that the code itself is cheap.

The brutal truth: rAF is necessary for smooth animations, but it is not sufficient. You still need discipline.

Measuring Performance Honestly: Frame Budget, Jank, and Reality

Performance discussions often collapse into hand-wavy claims about “smoothness.” Let's be precise. At 60Hz, you have about 16.67ms per frame to do everything: JavaScript, style calculation, layout, paint, and compositing. Exceed that budget, and the browser drops a frame. Drop enough frames, and users notice jank.

Using timers for animation eats into that budget unpredictably. Callbacks might fire too late or too often. rAF at least gives you a stable cadence. But measurement matters more than ideology. Chrome DevTools, Firefox Performance tools, and Safari's Timeline all show you frame timelines and long tasks. These tools exist because guessing is not engineering.

A common anti-pattern is “it feels smooth on my machine.” High-end laptops hide sins that low-end phones brutally expose. Real performance validation means testing under CPU throttling, with background tabs, and on devices with different refresh rates. Browser vendors explicitly recommend rAF for animations in their performance documentation, but they also emphasize minimizing work per frame.

If you want brutal honesty, here it is: if your animation logic routinely exceeds the frame budget, switching APIs won't save you. You need to reduce work, batch DOM writes, avoid forced synchronous layouts, and offload non-visual computation to Web Workers where possible. rAF is a coordination tool, not a performance miracle.

The 80/20 of Performant Animations: What Actually Moves the Needle

Most animation performance wins come from a surprisingly small set of practices. Roughly 20% of the effort delivers 80% of the results, and the rest is diminishing returns. First, use requestAnimationFrame for any animation that updates visuals every frame. This aligns your code with the rendering pipeline and avoids timer drift. Second, animate properties that the browser can composite efficiently, such as transform and opacity, instead of layout-affecting properties like top, left, or width. This guidance is consistent across MDN and browser vendor docs.

Third, minimize work inside your animation loop. Cache DOM references, precompute values when possible, and never trigger layout reads after writes in the same frame. Fourth, stop animations when they are not visible. rAF helps, but your logic should also be explicit about canceling unnecessary loops. Finally, measure. Use real profiling tools and look at frame charts, not vibes.

If you do only these five things, you will outperform the majority of production websites. Everything else—micro-optimizations, clever math, exotic scheduling tricks—comes later and only if profiling proves they matter.

Conclusion: Choose the Right Tool, Not the Familiar One

The web has matured, but habits lag behind. setTimeout and setInterval are not evil, but they are the wrong abstraction for frame-based animation. requestAnimationFrame exists because browsers render in frames, not milliseconds, and pretending otherwise leads to jank and wasted resources. This is not opinion; it is how modern browsers are built, as documented by WHATWG and MDN.

The honest takeaway is simple. If you animate visuals, use rAF. If you need delayed or repeated non-visual work, use timers. If performance matters—and it always does for user experience—measure relentlessly and respect the frame budget. Smooth animations are not about clever tricks; they are about alignment with the platform.

If this article feels blunt, that's intentional. Animation performance is a solved problem at the API level. What remains unsolved is developer discipline.