Pretext Lab
01The invisible cost
Arc 1 · Mental Model

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.

workload · 200 items
— fps

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.

400
200

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:

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:

"make the slider feel instant at the high end" stop reading from the DOM inside the input handler "no flash when the card mounts" know the card's height before you insert it "live drag should feel like moving paper" avoid getBoundingClientRect in pointermove "responsive sweep with no stutter" pre-measure once, then answer all widths by arithmetic
Lesson 02 introduces the two-phase pattern that makes every one of these cheap.
the anti-pattern this demo runs
// 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';
});