Testing

requestAnimationFrame: Browser Support, Use Cases, Issues

requestAnimationFrame works on Chrome 22+, Edge 12+, Firefox 11+, Safari 6+, Opera 15+, and IE 10+. See per-browser support, throttling, and gotchas.

Author

Prince Dewani

May 6, 2026

requestAnimationFrame is a WHATWG HTML Living Standard JavaScript method that schedules a callback to run right before the browser paints the next frame. It works unprefixed in Chrome 22+, Edge 12+, Firefox 11+, Safari 6+ on macOS and iOS, Opera 15+, Samsung Internet 4+, Android Browser 4.4+, and Internet Explorer 10+, while Opera Mini never added support.

This guide covers what requestAnimationFrame is, the browsers that support it, how it works under the hood, how it compares to setTimeout, the production use cases it powers, and the known issues you will hit when shipping animations across browsers.

What is requestAnimationFrame?

requestAnimationFrame is a method on the WHATWG AnimationFrameProvider mixin, exposed on the global window. It accepts a callback and returns an integer ID. The browser invokes the callback once, just before the next paint, and passes a high-resolution timestamp to the callback so animations can scale by elapsed time.

Which browsers does requestAnimationFrame support?

requestAnimationFrame is supported unprefixed in every modern engine. Older Chrome and Firefox builds shipped vendor-prefixed variants, and Opera Mini still has no support at all.

Loading browser compatibility data...

requestAnimationFrame compatibility in Chrome

Chrome supports requestAnimationFrame unprefixed from Chrome 22 on Windows, macOS, Linux, ChromeOS, and Android. Chrome 10 to 21 only exposed it as webkitRequestAnimationFrame, and Chrome 4 to 9 did not support it. Chrome for Android matches the desktop timeline from Chrome Android 25 on.

requestAnimationFrame compatibility in Edge

Edge supports requestAnimationFrame unprefixed from Edge 12 on Windows 10 and macOS. Chromium-based Edge 79 and later inherits the same engine timing as Chrome, so behaviour and high-refresh-rate handling match Chrome version for version.

requestAnimationFrame compatibility in Firefox

Firefox supports requestAnimationFrame unprefixed from Firefox 11 on Windows, macOS, Linux, and Android. Firefox 4 to 10 shipped it as mozRequestAnimationFrame, and Firefox 2 to 3.6 did not support it. Firefox Android tracks the desktop timeline.

requestAnimationFrame compatibility in Safari

Safari supports requestAnimationFrame from Safari 6 on macOS and from Safari 6 on iOS and iPadOS. Safari 3.1 to 5.1 did not support it on either platform. Safari clamps the rate to the device refresh rate, including 120Hz on ProMotion iPhones and iPads.

requestAnimationFrame compatibility in Opera

Opera supports requestAnimationFrame from Opera 15 on Windows, macOS, and Linux, after the engine switch to Blink. Opera 9 to 12.1 did not support it. Opera Mini does not support requestAnimationFrame on any version because the proxy renderer skips per-frame JavaScript.

requestAnimationFrame compatibility in Samsung Internet

Samsung Internet supports requestAnimationFrame from Samsung Internet 4 on Android. Behaviour and throttling rules track the underlying Chromium build, so a Galaxy device on Samsung Internet 24 paints at the same cadence as Chrome Android 122.

requestAnimationFrame compatibility in Android Browser

The legacy Android Browser supports requestAnimationFrame from Android 4.4 onward, when WebView switched to the Chromium engine. Android 2.1 to 4.3 used the older WebKit-based WebView and did not support it.

requestAnimationFrame compatibility in Internet Explorer

Internet Explorer supports requestAnimationFrame from IE 10 on Windows 7 and later. IE 5.5 to 9 did not support it, so a setTimeout shim is required for any IE 9 fallback. Microsoft has retired Internet Explorer, so most teams can drop the shim now.

Note

Note: requestAnimationFrame timing varies across Safari, Firefox, and Chrome on high-refresh-rate displays and throttled background tabs. Test it on real browsers and OS with TestMu AI. Try TestMu AI free!

How does requestAnimationFrame work?

requestAnimationFrame plugs into the browser's rendering loop. The engine batches your callback with style, layout, and paint so the work lands in the same frame the browser was about to render anyway.

  • Queue the callback: Calling requestAnimationFrame(cb) appends cb to the document's animation frame callbacks list and returns a numeric handle.
  • Wait for the next paint: The browser's event loop reaches the rendering steps once it is ready to paint, which usually happens at the display refresh rate.
  • Run all callbacks: Every queued callback runs in order with the same DOMHighResTimeStamp argument, so any DOM reads and writes inside them target one consistent frame.
  • Style, layout, paint: The browser then runs style recalculation, layout, and paint, and any property mutations from the callback land in this frame instead of the next one.
  • Cancel or re-schedule: The queue is now empty. Call requestAnimationFrame again from inside the callback to keep animating, or call cancelAnimationFrame(handle) to drop a pending entry.

Use the timestamp argument to scale motion by elapsed time. Without it, animations run twice as fast on a 120Hz ProMotion iPad as they do on a 60Hz monitor.

// Detect support and start a smooth, time-scaled animation.
if (typeof window.requestAnimationFrame === "function") {
  const box = document.getElementById("box");
  let firstFrame;

  function step(now) {
    if (firstFrame === undefined) firstFrame = now;
    const elapsed = now - firstFrame;
    // 0.2 px per millisecond, capped at 300 px so it stops cleanly.
    const x = Math.min(elapsed * 0.2, 300);
    box.style.transform = "translateX(" + x + "px)";
    if (x < 300) requestAnimationFrame(step);
  }

  const rafId = requestAnimationFrame(step);
  // Cancel before unloading the page so the queue stays clean.
  window.addEventListener("beforeunload", () => cancelAnimationFrame(rafId));
} else {
  console.log("requestAnimationFrame is not supported in this browser.");
}

requestAnimationFrame vs setTimeout

Both APIs schedule a callback, but only requestAnimationFrame is aware of the rendering pipeline. The table compares the dimensions that decide which one to reach for.

DimensionrequestAnimationFramesetTimeout
Timing sourceDisplay refresh rate, synced with the next paintFixed millisecond delay you pass in
Frame alignmentAlways runs once per frame, before style and layoutFires whenever the timer expires, often mid-frame
Background tab behaviourPaused when the tab is hidden or the iframe is offscreenThrottled to about 1 call per second, but still runs
High refresh rate handlingScales with 90Hz, 120Hz, and 144Hz displays automaticallyLocked to the delay you set, so motion drifts on faster screens
Battery and CPU costLower, because the browser skips the callback when no paint is neededHigher, because the timer fires regardless of paint state
Best fitAnimations, canvas redraws, scroll-driven effects, game loopsPlain delays, retries, debounced one-shot work

What are the use cases of requestAnimationFrame?

requestAnimationFrame is the default loop for any work that has to stay in step with the screen. The most common production patterns are below.

  • Canvas and WebGL rendering: Game engines like Phaser, PixiJS, and Three.js drive their render loop from requestAnimationFrame so the GPU work matches the display refresh rate.
  • Smooth DOM animations: Libraries like GSAP, Framer Motion, and Motion One use it to mutate transform and opacity once per frame, which avoids layout thrash and keeps motion at 60 frames per second.
  • Scroll-driven effects: Apps like Linear and Notion debounce scroll handlers behind requestAnimationFrame so heavy work runs at most once per paint, not on every wheel event.
  • Force a layout boundary: A single requestAnimationFrame after a class change lets the browser commit the previous style change so the next change actually triggers a CSS transition.
  • Frame-rate measurement: Performance dashboards in apps like Figma compute the delta between two timestamps to estimate FPS without touching the deprecated chrome.fps API.
  • Idle work batching: Pair requestAnimationFrame with requestIdleCallback to start work on the next paint and yield back if the frame budget is already burned.
...

What are the known issues with requestAnimationFrame?

requestAnimationFrame is well supported, but the corner cases bite production code in predictable ways.

  • High-refresh-rate drift: A callback that moves an element by a fixed pixel count per call doubles its speed on a 120Hz iPad and triples on a 144Hz monitor. Always scale by the timestamp delta.
  • Background tab freeze: Hidden tabs pause the queue, so timers built on requestAnimationFrame stop ticking until the tab is visible. Pair it with the Page Visibility API or fall back to setTimeout for elapsed-time math.
  • Leaked loops: Forgetting to call cancelAnimationFrame on unmount keeps the loop alive after the React component or Vue instance is gone, which leaks memory and burns CPU on dead nodes.
  • Forced reflow inside the callback: Reading offsetHeight or getBoundingClientRect after a style mutation in the same callback forces a synchronous layout. Batch reads first, writes second, or split work across two frames.
  • Long-task violations: Chrome logs "[Violation] requestAnimationFrame handler took N ms" when a single callback runs longer than 50 milliseconds, which means the next frame missed its deadline. Profile and split the work.
  • Worker support is partial: DedicatedWorkerGlobalScope.requestAnimationFrame paired with OffscreenCanvas only ships in Chromium browsers. In my experience, Safari and Firefox still need the canvas pipeline on the main thread, so OffscreenCanvas-only ports break in production until shipped behind a UA check.
...

Citations

All requestAnimationFrame version numbers and platform notes in this guide come from these primary sources:

Author

Prince Dewani is a Community Contributor at TestMu AI, where he manages content strategies around software testing, QA, and test automation. He is certified in Selenium, Cypress, Playwright, Appium, Automation Testing, and KaneAI. Prince has also presented academic research at the international conference PBCON-01. He further specializes in on-page SEO, bridging marketing with core testing technologies. On LinkedIn, he is followed by 4,300+ QA engineers, developers, DevOps experts, tech leaders, and AI-focused practitioners in the global testing community.

Open in ChatGPT Icon

Open in ChatGPT

Open in Claude Icon

Open in Claude

Open in Perplexity Icon

Open in Perplexity

Open in Grok Icon

Open in Grok

Open in Gemini AI Icon

Open in Gemini AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Frequently asked questions

Did you find this page helpful?

More Related Hubs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests