The invisible cost
Open the web app you're most reluctant to use — the one whose slider stutters, the one whose chat bubbles jump a pixel when they mount. Underneath almost every instance of that feeling is a synchronous DOM measurement: the renderer was asked, in the middle of an event handler, "how tall will this be?"
Here's the shape. Drag the width slider — the three paragraphs below reflow in real time. Then crank the workload slider, which controls how many items the handler pretends to need to measure on every drag event. Watch the FPS number in the top-right corner.
The letter rests on the line. The line rests on the page. The page rests on the table. Nothing, so far, is busy.
Attention is the rarest and purest form of generosity. The mind turns toward what it values and learns by turning — the movement of attention is itself the learning.
Between the word you just read and the next one lies a small country where reading is not happening. Travelers there come back changed.
Mechanism
When you drag a slider, the browser fires an input event on every tiny movement — often more than a hundred times a second. A naive handler does its downstream work right there, synchronously. If that work includes reading measurements from the DOM — getBoundingClientRect, offsetHeight, getComputedStyle — each read forces the browser to flush any pending layout first.
Do two thousand of those reads inside one handler and the handler runs longer than a frame takes. The frame is missed. Another input arrives. Its handler also misses a frame. The slider starts to lag your cursor. This is layout thrashing, and it is the number-one reason rich web UIs feel sluggish.
Try it: set the workload to 2000 and drag the width. On any machine the cursor pulls ahead of the slider, and FPS falls toward the single digits.
Application
Much of what makes a page feel alive is blocked by this cost. Consider the designs that should be ordinary but rarely are:
- A pull-quote whose font size breathes with the viewport, no reflow flash.
- A chat bubble that knows its height before insertion, so nothing else shifts.
- A masonry grid that reflows continuously as the window resizes.
- Text that wraps around a draggable image — text pinned, image moved, text re-routing around the obstacle at 60 fps.
The browser's text engine is fully capable of each of these. It's the measurement step — reading back what the engine decided — that blocks them.
"Time is but the stream I go a-fishing in. I drink at it; but while I drink I see the sandy bottom and detect how shallow it is. Its thin current slides away, but eternity remains."
Henry David Thoreau, Walden (1854)Direct Claude
These are phrasings that point at the fix introduced in the next lesson:
getBoundingClientRect in pointermove
"responsive sweep with no stutter"
→
pre-measure once, then answer all widths by arithmetic
// Probe-based DOM measurement, synchronously, inside an input handler.
// Each iteration forces a layout flush. At high workload, the handler
// runs longer than a frame takes.
widthSlider.addEventListener('input', (e) => {
const width = parseFloat(e.target.value);
for (let i = 0; i < workload; i++) {
probe.textContent = items[i % items.length];
probe.style.width = width + 'px';
void probe.getBoundingClientRect().height; // forces layout
}
textRegion.style.width = width + 'px';
});