Why requestAnimationFrame Beats setTimeout: Deep Dive into the Browser Event Loop
requestAnimationFrame lets browsers schedule animation callbacks just before the next repaint, offering smoother 60 Hz updates compared to setTimeout; this article explains its API, demonstrates code examples, explores the event loop, task and microtask queues, rendering timing, cross‑browser quirks, and best practices for efficient animation and task splitting.
1. What is requestAnimationFrame
window.requestAnimationFrame()tells the browser that you want to run an animation and asks it to invoke the supplied callback before the next repaint. The callback runs just before the browser redraws the page.
const test = document.querySelector<HTMLDivElement>("#test")!;
let i = 0;
function animation() {
if (i > 200) return;
test.style.marginLeft = `${i}px`;
window.requestAnimationFrame(animation);
i++;
}
window.requestAnimationFrame(animation);On a typical 60 Hz display the code runs about 60 times per second (≈16.6 ms per frame). The API returns a request ID that can be passed to cancelAnimationFrame() to stop the callback.
const test = document.querySelector<HTMLDivElement>("#test")!;
let i = 0;
let requestId: number;
function animation() {
test.style.marginLeft = `${i}px`;
requestId = requestAnimationFrame(animation);
i++;
if (i > 200) {
cancelAnimationFrame(requestId);
}
}
animation();2. Comparison with setTimeout
Using a zero‑delay setTimeout can mimic requestAnimationFrame, but the visual result differs because setTimeout may fire many times before the browser gets a chance to render.
const test = document.querySelector<HTMLDivElement>("#test")!;
let i = 0;
let timerId: number;
function animation() {
test.style.marginLeft = `${i}px`;
timerId = setTimeout(animation, 0);
i++;
if (i > 200) {
clearTimeout(timerId);
}
}
animation();3. Event Loop and requestAnimationFrame
The browser’s event loop coordinates tasks, microtasks, rendering and idle periods on the main thread.
3.1 Task queue
Each event loop has one or more task queues (sets of tasks). The loop selects the first runnable task, executes it, then runs the microtask checkpoint.
3.2 Task sources
Tasks can originate from DOM operations, user interactions, network I/O, history traversal, timers ( setTimeout, setInterval), IndexedDB, etc.
3.3 Microtasks
Microtasks are a true FIFO queue. Common sources are promises, MutationObserver, Object.observe (deprecated), and Node.js’s process.nextTick.
3.4 Event‑loop processing steps
Select a task queue that contains at least one runnable task; otherwise, proceed to microtasks.
Take the oldest task from the selected queue and mark it as the currently running task.
Execute the task.
Clear the running‑task reference.
Run the microtasks checkpoint (execute all microtasks).
Set hasARenderingOpportunity to false.
Update rendering.
If there are no remaining tasks, an empty microtask queue, and no rendering opportunity, run an idle period via requestIdleCallback.
Return to step 1.
During the rendering step the browser processes resize/scroll events, runs CSS animations, executes requestAnimationFrame callbacks, and finally paints the document. Therefore requestAnimationFrame callbacks are invoked just before the style/layout/paint phases, ensuring smooth visual updates.
4. Cross‑browser differences
The following example demonstrates a discrepancy between Chrome/Firefox and Safari.
test.style.transform = 'translate(0, 0)';
document.querySelector('button').addEventListener('click', () => {
const test = document.querySelector('.test');
test.style.transform = 'translate(400px, 0)';
requestAnimationFrame(() => {
test.style.transition = 'transform 3s linear';
test.style.transform = 'translate(200px, 0)';
});
});In Chrome and Firefox the element moves right by 200 px because the requestAnimationFrame callback runs before CSS style calculation. Safari executes the callback after the current frame, so the element starts at 400 px and moves left to 200 px.
5. Reproducing Safari timing
To obtain Safari‑like behaviour in a standards‑compliant environment, nest an additional requestAnimationFrame call so that the inner callback runs in the next frame.
test.style.transform = 'translate(0, 0)';
document.querySelector('button').addEventListener('click', () => {
const test = document.querySelector('.test');
test.style.transform = 'translate(400px, 0)';
requestAnimationFrame(() => {
requestAnimationFrame(() => {
test.style.transition = 'transform 3s linear';
test.style.transform = 'translate(200px, 0)';
});
});
});6. Other uses of requestAnimationFrame
Because setTimeout can cause dropped frames, requestAnimationFrame should be preferred for visual updates. It can also be used to split large JavaScript tasks across frames, preventing a single long task from blocking rendering.
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var taskFinishTime;
do {
var nextTask = taskList.pop();
processTask(nextTask);
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);
if (taskList.length > 0) {
requestAnimationFrame(processTaskList);
}
}7. References
MDN – https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame
CodeSandbox example – https://codesandbox.io/s/raf-ycqc3
HTML spec – Event Loop – https://html.spec.whatwg.org/multipage/webappapis.html#event-loop
Generic task sources – https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources
Event loop processing model – https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
Medium article on JavaScript main thread – https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23
Jake Archibald’s site – https://jakearchibald.com/
JSConf talk – https://www.youtube.com/watch?v=cCOL7MC4Pl0
Google Web Fundamentals – Optimize JavaScript Execution – https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution
Aotu Lab
Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.
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.
