Animation, Idle Work, and Workers
Some performance work is about choosing the right time and place to run code.
The browser gives you APIs for animation, low-priority work, and background processing.
requestAnimationFrame()
Use requestAnimationFrame() when JavaScript updates something visual.
The callback runs before the browser's next paint.
function moveBox(position) {
requestAnimationFrame(() => {
box.style.transform = `translateX(${position}px)`;
});
}This helps visual updates line up with rendering.
Animation Loop
let x = 0;
function animate() {
x += 2;
box.style.transform = `translateX(${x}px)`;
if (x < 300) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);This is better for animation than repeatedly using setInterval().
The browser can pause or slow animation work when the tab is not visible.
requestIdleCallback()
requestIdleCallback() schedules low-priority work when the browser has idle time.
requestIdleCallback(() => {
preloadSuggestions();
});It can be useful for work that should not block important interactions.
Examples:
- preloading optional data
- preparing a cache
- logging analytics
- cleaning up old data
Idle Work Must Be Optional
Idle callbacks may run later than expected.
They may not run at all in some situations or environments.
Do not put critical work only in requestIdleCallback().
if ("requestIdleCallback" in window) {
requestIdleCallback(preloadSuggestions);
} else {
setTimeout(preloadSuggestions, 1000);
}Use a fallback when needed.
Web Workers
Web Workers let you run JavaScript on a background thread.
They are useful for CPU-heavy work that would otherwise block the main thread.
Main file:
const worker = new Worker("worker.js");
worker.postMessage({ type: "analyze", items });
worker.addEventListener("message", (event) => {
renderAnalysis(event.data);
});Worker file:
self.addEventListener("message", (event) => {
const result = analyzeItems(event.data.items);
self.postMessage(result);
});The main thread sends data to the worker.
The worker sends results back.
Worker Tradeoffs
Workers are powerful, but they are not free.
Consider:
- data must be copied or transferred between threads
- workers cannot directly access the DOM
- setup adds complexity
- small tasks may be slower because of messaging overhead
Workers are best for meaningful CPU-heavy work.
Examples:
- parsing large files
- image processing
- complex calculations
- searching or transforming large datasets
Transferring Data
Large binary data can sometimes be transferred instead of copied.
worker.postMessage(arrayBuffer, [arrayBuffer]);After transfer, the original arrayBuffer is no longer usable on the sending side.
This can improve performance for large data, but it requires care.
Choosing the Right API
| Goal | Good Option |
|---|---|
| update visual state before paint | requestAnimationFrame() |
| run low-priority optional work | requestIdleCallback() |
| avoid blocking with CPU-heavy work | Web Worker |
| run code after a delay | setTimeout() |
Common Mistakes
- Using
setInterval()for animations that should follow browser paint. - Putting critical logic in
requestIdleCallback()with no fallback. - Moving tiny tasks to workers and adding overhead for no benefit.
- Trying to change the DOM directly inside a worker.
- Sending huge data repeatedly between the main thread and a worker without considering transfer costs.
Summary
Use requestAnimationFrame() for visual updates, requestIdleCallback() for optional low-priority work, and Web Workers for meaningful CPU-heavy tasks.
These APIs do not make slow code disappear, but they help keep the main thread responsive.