The Browser Hates Surprises

Durgesh Rajubhai Pawar Durgesh Rajubhai Pawar on

We often treat the browser like a canvas — a blank slate waiting for us to paint pixels. But this mental model is flawed. The browser isn’t really a painter; it’s a constraint solver.

Every time you load a page, you enter a high-speed negotiation. You provide the rules (HTML & CSS), and the browser calculates the geometry. When you give it all the math upfront, the result feels magical: a stable, buttery-smooth experience.

But when we force the browser to recalculate that geometry mid-stream — because an image loaded late, a scrollbar popped in, or a font swapped — we break the spell.

We know this phenomenon as “Cumulative Layout Shift” (CLS), but really, it’s just jank. And jank doesn’t happen by accident. It happens because we surprised the browser.

To fix this, we need to stop fighting the rendering engine and start orchestrating it. But first, seeing is believing. Let’s look at exactly what it looks like when we get it wrong.

A “Hostile” Web Site

I call what we’re doing in the demo below “hostile” because the code is indifferent to the browser’s needs. It treats the rendering engine like a bucket we can dump data into whenever it arrives.

In the code below, you will see four distinct “surprises” that break the user experience:

  1. The Sticky Header Collision: When you click “Jump to Section 2,” the browser scrolls correctly to the top of the element, but the title gets buried behind the fixed header.
  2. Popcorn Loading: The text and images load independently. The layout jumps once for the text, and again for the image.
  3. The Image Shift: The image isn’t reserved space. It pushes the text down when it finally arrives.
  4. The Scrollbar Shift: When the content grows long enough, a physical scrollbar pops in (on Windows/Linux), shifting the entire UI to the left.

🛠 The “Before” Demo

Note that we’re just faking this hostile behavior with setTimeout, but it’s entirely plausible that real world websites experience these conditions naturally. Data can take time to arrive from APIs. Media can be slow to load. The amount of content can push a page to needing to scroll when it didn’t before.

The Theory (Why This Happens)

Why did that feel so broken? It wasn’t just “slow internet.” It was a failure of negotiation.

To fix this, we need to understand how the browser thinks. The browser rendering engine has a strict pipeline:

  1. Parse (Read HTML/CSS)
  2. Layout (Calculate the geometry of every box)
  3. Paint (Fill in the pixels)

The “Streaming Buffer” Mistake

In the code above, we threw the text at the browser, then the image, then the scrollbar.

Every time we did that, we forced the browser to stop Painting, go back to Layout, recalculate the math for the page, and then Paint again. This is called a Reflow. Reflows are expensive for the CPU, but they are disastrous for the user experience because they physically move pixels that the user is currently looking at.

We need to move from Reactive Rendering (reacting to data arrival) to Orchestrated Rendering (planning for data arrival).

Solutions

We are going to make four specific negotiations with the browser to ensure stability.

1. The Coordinate Negotiation

The browser isn’t “wrong” when it scrolls your title behind the sticky header. It is scrolling to the exact mathematical top of the element. The problem is that our header exists outside the normal document flow.

We need to update the browser’s metadata regarding that element’s landing zone.

:target {
  /* "Hey browser, when you scroll here, leave 6rem of space against the top" */
  scroll-margin-top: 6rem;
}Code language: CSS (css)

2. The Space Negotiation

On Windows and Linux, standard scrollbars take up physical space (usually ~17px). This happens on macOS too, but only when users have the Show scroll bars: Always option selected, which is not the default. When a scrollbar appears, the available width of the viewport changes, forcing a global recalculation that shifts centered content to the left.

We have two ways to solve this layout mutation.

Option A: The Classic Fix (Maximum Compatibility)

The most reliable method is to force the scrollbar track to be visible at all times, even on short pages.

html {
  overflow-y: scroll;
}Code language: CSS (css)

Option B: The Modern Fix (Cleaner UI)

Modern CSS gives us a dedicated property that tells the browser: “If a scrollbar might exist later, reserve that 17px slot now, but don’t show an ugly disabled track.”

html {
  scrollbar-gutter: stable;
}Code language: CSS (css)

3. The Layout Reservation

Normally, the browser assumes an image is 0x0 until the file header downloads. By setting an aspect-ratio in CSS, we issue a “reservation ticket.” We allow the browser to calculate the final bounding box during the CSS Parse phase — before the network request is even sent.

img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
Code language: CSS (css)

4. The Orchestration (Promise.all)

Instead of firing three separate state updates (three separate reflows), we wait for the entire “scene” to be ready. We trade a few milliseconds of “First Paint” for a UI that arrives fully formed.

// Don't just fetch. Orchestrate.
async function loadScene() {
  // Wait for critical actors to be ready
  const results = await Promise.all([
    fetch('/api/text'),
    fetch('/api/image')
  ]);

  // Update the state ONCE. 
  // One reflow. One paint.
  setFullScene(results);
}Code language: JavaScript (javascript)

Phase 4: The “Stable” Application

Here is the exact same application, but negotiated correctly.

Notice how “calm” the loading feels. The layout never jumps. The scroll lands exactly where you expect. It feels like a native application because we gave the browser the constraints it needed before painting.

🛠 The “After” Demo

Conclusion

Optimization is not about making things load faster; it is about making them load calmer.

Every scroll bug, every jumpy image, and every layout shift is a sign that we failed to give the browser the information it needed at the time it needed it.

Stop surprising the browser. Start reserving space.

Need to learn about web performance?

Leave a Reply

Your email address will not be published. Required fields are marked *

$966,000

Frontend Masters donates to open source projects through thanks.dev and Open Collective, as well as donates to non-profits like The Last Mile, Annie Canons, and Vets Who Code.