Have you ever noticed your animations feel janky, the scroll feels laggy or the UI stutters even on powerful machines?
This is likely due to layout thrashing, a common yet often misunderstood performance killer in frontend development.
In this article, I’ll explain what layout thrashing is, why it occurs and how to overcome it and build smooth, high performance animations.
What is Layout Thrashing?
Layout thrashing occurs when JavaScript repeatedly forces the browser to recalculate the page layout synchronously. This disrupts the browser’s ability to efficiently batch render work. That’s the formal definition. Let’s simplify it.
Browsers typically batch layout and paint operations to avoid repeatedly calculating heavy layout computations. However, layout thrashing prevents this batching, which might sound counterintuitive, let’s see why.
Consider the following snippet:
window.addEventListener("scroll", () => {
updateUI();
});
function updateUI() {
const top = box.getBoundingClientRect().top; // forces layout
box.style.transform = `translateY(${top * 0.5}px)`; // invalidates layout
}
This snippet demonstrates how layout thrashing can occur. The updateUI function is triggered every time the user scrolls, causing the browser to recalculate the layout synchronously (often dozens of times per second).
const top = box.getBoundingClientRect().top; // forces layout
box.style.transform = translateY(${top * 0.5}px); // invalidates layout
This is a classic example of layout thrashing. The getBoundingClientRect() function forces layout calculations immediately, but the box.style.transform invalidates those calculations when these operations run in a loop. This creates a messy cycle of read and write operations instead of clean batched layout updates.
So, the above approach forces the browser into a cycle:
layout → paint → layout → paint → layout → paint → …
Instead, the browser should leverage batched updates, which aim to:
- Collect all DOM reads
- Collect all DOM writes
- Compute layout once
- Paint once
- Composite once
This results in a simpler layout-paint cycle:
Layout → Paint
Why Layout Thrashing Is Serious?
At this point, you might think. It’s just a couple of extra reads and writes, so what’s the big deal? Let me explain.
Modern browsers are highly optimised, but layout calculations are expensive.
When layout thrashing occurs, it results in two problems:
- The browser cannot batch layout work.
- It is forced into sync reflow loops.
As a result, the UI becomes jittery even on powerful machines. This is why even MacBooks and gaming laptops can show jank on poorly optimised UI code.
How to Overcome Layout Thrashing?
It’s possible to prevent layout thrashing by using a couple of techniques, one of which is using requestAnimationFrame() (rAF).
rAF is a web API supported by all modern browsers. It optimises layout updates by instructing the browser to call a specific function before each repaint. For example, consider the previous example but with rAF:
let latestScroll = 0;
let ticking = false;
window.addEventListener("scroll", () => {
latestScroll = window.scrollY;
if (!ticking) {
requestAnimationFrame(() => {
box.style.transform = `translateY(${latestScroll * 0.3}px)`;
ticking = false;
});
ticking = true;
}
});
Now, what happens behind the scenes is that our rAF batches multiple scroll events into a single frame update. and provides the browser with the latest one. This allows the browser to pick it up and apply the computation. Instead of a ever ending loop of read and write, it’s simply:
READ → READ → READ → WRITE → WRITE → WRITE → PAINT
In simpler terms:
Without rAF (BAD): READ → WRITE → PAINT → READ → WRITE → PAINT
With rAF (GOOD): READ → READ → WRITE → WRITE → PAINT
Additionally, there are other helpful tips for improving animations:
- Prefer transform and opacity
- Avoid animating layout properties
- Use rAF for scroll and mouse animations
- Virtualise long lists
- Reduce forced reflows
- Use CSS animations where possible
- Prefer GPU-based animations over JS/layout-based animations
Key Takeaways
Layout thrashing is a silent performance killer. Understanding and fixing it separates good UIs from great ones. Once you design your UI to cooperate with the browser, smooth performance becomes effortless.
Top comments (1)
Really nice technique. Even though I'm not a Java script dev I can understand what you're trying to implement. Keep writing