DEV Community

Cover image for Layout Thrashing - The Hidden Performance Killer in Modern Web Apps
Gokul
Gokul

Posted on

Layout Thrashing - The Hidden Performance Killer in Modern Web Apps

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
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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  
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. The browser cannot batch layout work.
  2. 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;
  }
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

In simpler terms:

Without rAF (BAD): READ  WRITE  PAINT  READ  WRITE  PAINT
With rAF (GOOD): READ  READ  WRITE  WRITE  PAINT
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
nithish_kumar_4c8b1455276 profile image
NITHISH KUMAR

Really nice technique. Even though I'm not a Java script dev I can understand what you're trying to implement. Keep writing