bi-09 — Analysis
Required invariants
- Paint produces a recording, not pixels. Display items are drawing commands; raster happens later, elsewhere, possibly on another thread or process.
- A paint chunk is a run of display items sharing one property tree state. That definition is what makes property-tree-only updates cheap.
- Paint order is not DOM order. Stacking contexts are atomic;
z-indexis scoped to them. - Cached paint output is reused when nothing that affects it changed — at display-item and at subsequence granularity.
- Hit testing walks the painted representation in reverse paint order, and the compositor needs its own copy of hit-test data to answer input without the main thread.
The mechanism behind the advice
"Animate transform and opacity" is usually justified by "the GPU is fast." That explanation is
wrong and it mispredicts.
The correct chain:
transform/opacity change → a property-tree node value changes
→ display list unchanged, so no re-record
→ tiles unchanged, so no re-raster
→ compositor produces a frame from existing tiles
→ the main thread is not involved
Stages were skipped; a processor was not made faster. This predicts the exceptions correctly:
filter: blur() is also composited and still expensive, because the effect itself costs GPU work
per frame. A hardware explanation cannot tell you that.
Failure modes
| Break | Consequence |
|---|---|
| Bake transforms into display items | the cheap animation path becomes impossible |
| Paint in DOM order | overlapping content renders wrong |
| Skip paint invalidation for a property | stale pixels |
will-change on thousands of elements | memory blow-up; the "optimisation" loses |
| Independent snapping of adjacent boxes | 1px seams, blurry borders |
Sub-pixel layout, integer pixels
Layout uses LayoutUnit (fixed point, 1/64 px); paint must produce device pixels. Integer layout
would accumulate error — 100 boxes each rounded up 0.4px is a 40px drift — so positions stay precise
and snapping happens at paint time.
That is why "why is this blurry" is a paint-time question about the effective transform, not a CSS question, and why a fractional ancestor transform can make a whole subtree blurry mid-animation and crisp at rest.
The recurring structure
Mark-then-walk-then-invalidate appears here (PrePaintTreeWalk), in style (bi-07), and in layout
(bi-08). Three subsystems, one pattern: record cheaply during mutation, resolve precisely once
per frame. It is what makes the pipeline incremental, and it is why interleaving a read into a
write loop is catastrophic rather than merely slow.