Understanding requestAnimationFrame: Usage, Timing, Performance, and Compatibility
This article explains the requestAnimationFrame API, demonstrates basic and cancelable animation examples, discusses its execution timing within the browser frame lifecycle, compares its performance to setTimeout, covers compatibility and polyfills, and shows how to wrap it in a React hook for practical use.
requestAnimationFrame (rAF) is a browser API that asks the browser to invoke a callback before the next repaint, allowing smooth animations. The MDN description is quoted to clarify its purpose.
Basic usage is shown with a React component that moves a square element by updating its marginLeft on each frame until a count limit is reached. The code demonstrates how the callback repeatedly calls window.requestAnimationFrame(animation) to create a loop.
class App extends React.Component {
componentDidMount() {
const test = document.querySelector("#test");
document.querySelector('button').addEventListener('click', () => { animation(); });
let count = 0;
function animation() {
if (count > 200) return;
test.style.marginLeft = `${count}px`;
count++;
window.requestAnimationFrame(animation);
}
}
render() {
return (
开始
);
}
}The cancelAnimationFrame function receives the numeric ID returned by rAF and can stop a scheduled callback. An example adds a "stop" button that stores the request ID in cancelReq and cancels it when clicked.
class App extends React.Component {
componentDidMount() {
const test = document.querySelector("#test");
let cancelReq;
document.querySelector('#start').addEventListener('click', () => { animation(); });
document.querySelector('#stop').addEventListener('click', () => { window.cancelAnimationFrame(cancelReq); });
let count = 0;
function animation() {
if (count > 200) return;
test.style.marginLeft = `${count}px`;
count++;
cancelReq = window.requestAnimationFrame(animation);
}
}
render() {
return (
开始
停止
);
}
}Execution timing is explored by comparing two transform assignments within a single frame. Because rAF callbacks run after JavaScript execution but before Layout and Paint, the later assignment overwrites the earlier one, resulting in the element moving to the second position (rightward) despite intuition.
Testing shows that browsers typically invoke the rAF callback about 60 times per second (≈16.66 ms per frame). By logging the DOMHighResTimeStamp argument, the article demonstrates this interval and explains that higher refresh‑rate screens will run the animation faster if the movement per frame is constant.
To keep animation speed consistent across devices, the article suggests throttling updates using the timestamp and only applying changes when a desired elapsed time (e.g., >30 ms) is reached. This results in an effective interval of roughly two frames (≈33 ms).
Compatibility information is provided via a table image, concluding that rAF enjoys good browser support and can be polyfilled with setTimeout when unavailable.
export const scheduleTimeout = typeof setTimeout === 'function' ? setTimeout : (undefined:any);
const localRequestAnimationFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : scheduleTimeout;The article then compares a pure setTimeout loop to rAF. The setTimeout version runs slightly faster because multiple callbacks can execute within a single frame before the browser paints, leading to shorter intervals than the 16.6 ms rAF cadence.
Finally, a React hook wrapper useAnimationFrame is introduced, showing how to encapsulate rAF in a reusable hook. The source of the hook is displayed, highlighting the use of useLayoutEffect , useRef , and cancellation via cancelAnimationFrame .
import { useLayoutEffect, useRef } from "react";
export default (cb) => {
if (typeof performance === "undefined" || typeof window === "undefined") return;
const cbRef = useRef();
const frame = useRef();
const init = useRef(performance.now());
const last = useRef(performance.now());
cbRef.current = cb;
const animate = (now) => {
cbRef.current({ time: (now - init.current) / 1000, delta: (now - last.current) / 1000 });
last.current = now;
frame.current = requestAnimationFrame(animate);
};
useLayoutEffect(() => {
frame.current = requestAnimationFrame(animate);
return () => frame.current && cancelAnimationFrame(frame.current);
}, []);
};The article ends with a list of related React series articles, indicating its place within a broader tutorial collection.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.