An image finishes loading and the paragraph you were reading jumps down the page. Everyone has experienced it; on a slow connection some people have tapped the wrong thing because of it. The fix comes in two parts, and the first one is free.

Part one: reserve the space

The jump happens because the browser does not know how tall the image will be until enough of it has arrived to read the dimensions. Until then it lays out the page as though the image were nothing, then re-flows everything below when the real size turns up.

Telling it in advance costs two attributes:

<img src="/v1/hero.jpg?w=800" width="800" height="450" alt="…">

Modern browsers use those numbers to compute an aspect ratio and reserve a correctly proportioned box before a single byte of image data arrives. The image still scales responsively — width: 100%; height: auto in your stylesheet overrides the rendered size while the ratio is retained. The attributes are not a layout instruction any more; they are a shape declaration.

This costs nothing, works everywhere, and is skipped constantly. Do it before anything below.

Part two: put something in the box

A correctly sized empty rectangle is a solved layout problem and an unsolved perception problem. The page no longer moves, but it looks broken while it loads. That is what placeholders are for, and there are three tiers.

A single colour

The image's average colour as a CSS background. Costs about seven bytes as a hex triplet, adds no request, and is a genuine improvement on grey. For thumbnails and avatars this is often enough — nobody studies a 48 px square while it loads.

A tiny image

Encode the picture at something like 20 px wide, inline it as a data URI, and blur it with CSS. The shape of the content shows through, so a face reads as a face. The cost is real though: even a tiny JPEG carries headers and quantisation tables, so you are typically adding several hundred bytes to the HTML per image. On a grid of forty products that adds up to something you would not choose to send.

A BlurHash

A compact string that encodes a handful of low-frequency components of the image — the same discrete cosine transform idea JPEG uses, kept only at the coarsest scales, then packed into ASCII. With the common 4 × 3 component setting the result is around thirty characters:

LEHV6nWB2yk8pyo0adR*.7kCMdnj

That is small enough to sit in a JSON payload beside a product's name and price without anyone noticing. The client expands it into a smooth gradient that matches the image's composition — light where the image is light, warm where it is warm — and cross-fades to the real thing.

Compare the tiers honestly: thirty bytes against several hundred for a tiny JPEG, for a result that is blurrier but usually indistinguishable once it is behind a blur filter anyway. The tradeoff is a little client-side work to decode it, which is a small amount of arithmetic over a few dozen coefficients.

Fetch it with the data, not as a request

The one mistake that undoes the whole exercise is treating a placeholder as another image to download. If the client has to make a request to find out what to show while it waits for a request, you have added a round trip to save a round trip.

A placeholder belongs in the payload that describes the content — the JSON from your API, or the HTML itself. Fetch it once when you first process the image, store it beside the record, and serve it with everything else.

Where to stop

Placeholders are a perceived-performance technique, which means their value depends on how long the wait is. On a fast connection to a nearby edge the placeholder may be visible for a single frame. On a slow mobile connection it is the page for a second or more.

So: reserve the space everywhere, because it is free and it fixes a real usability problem. Add placeholders where the image is large, above the fold, or the main content — and do not bother generating them for every icon on the page.