Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 6.2Intermediate ~20 min · shot 2 of 7

What Each Property Costs

In one line

Animating a CSS property costs one of three things each frame: only compositing, or painting again, or laying out the page and painting. Hectal measured the cost of 40 properties in Chromium, so you can choose the cheap property from data rather than from folklore.

0/7 · 0%

Think of it like this

Moving house. Sliding a finished sofa across a room costs almost nothing, because the sofa is the same. Repainting the sofa each day costs a bit. Rebuilding the room's walls each day, so everything else must move, costs a lot. Properties come in the same three grades.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

composite-only
A change the compositor can make by moving or blending existing layers, with no main-thread layout or paint.
repaint
Drawing an element's pixels again, without changing any size or position.
paint flashing
A DevTools option that flashes green over every area the browser repaints.
trace
A recording of what the browser did, event by event, with times. Chromium traces show Layout and Paint events.

Step by step

01Animate nine properties at once

"The cost board" animates nine properties on nine swatches. Open the timeline and read each track's colour: it matches the table below.

02Read the table

In the table below, 0 for both Layout and Paint means the main thread did nothing. About 120 Paint events in 60 frames is a repaint on every frame, and about 61 Layout events is a layout on every frame.

03Compare a shadow done two ways

"A shadow on a big card" pulses the same shadow two ways: animating box-shadow (amber, paint every frame) or fading a pseudo-element that already has the finished shadow (green).

04Scale it up

Press 1 card / 24 cards with each variant. In my traces the shadow pulse recorded 182 Paint events in 1.5 s (about 12 ms of main-thread paint for one card, 29 ms for 24), and the opacity pulse none.

Try it yourself

  1. 1

    Change a property and read the colour

    In "The cost board" change the color row's property name to border-color (the values can stay) and press Run: its track stays amber, because border-color repaints like color. Change the background-color row to border-color too: that track turns from green to amber.

  2. 2

    Find the cheap shadow

    In "A shadow on a big card" press Shadow pulse, then pause and click its track: the cost mark is amber and the graph shows box-shadow changing. Press Opacity pulse: one track named div.card::after, green.

Code & diagrams

The cost board

Composite: `transform`, `opacity`, `background-color`. Paint: `box-shadow`, `border-radius`, `color`. Layout: `text-shadow`, `width`, `top`. WAAPI keyframes use camelCase (`backgroundColor`).

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

animating: transform, opacity, background-color, box-shadow, border-radius, color, text-shadow, width, top
Hectal's measured tabletext

Chromium 153.0.8010.12, headless, software rendering: your GPU browser may differ for some properties (filter above all). Regenerate with `node scripts/dsa-tools/measure-anim-cost.cjs`.

Hectal's measured table. Chromium 153, headless, 60 frames, main-thread events counted in a trace.
(Paint events: about two are recorded per painted frame.)

property             Layout  Paint   cost shown on the timeline
transform                 0      0   composite
opacity                   0      0   composite
backdrop-filter           0      0   composite
background-color          0      0   composite
clip-path                 0      0   composite
filter: blur()            0    122   paint
color                     0    124   paint
box-shadow                0    122   paint
border-radius             0    120   paint
text-shadow              61    122   layout
width                    61    122   layout
height                   61    183   layout
top, left, margin        61    122   layout
border-width             17     51   layout (it moves in whole-pixel steps)
A shadow on a big card

A: `box-shadow` itself animates (amber track). B: the finished shadow sits on `::after` and only its `opacity` animates (green track, targeting the pseudo-element). 24 cards make 24 animations.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

cards: 1, variant: shadow, animations: 1
cards: 1, variant: opacity, animations: 1
cards: 24, variant: opacity, animations: 24
cards: 1, variant: opacity, animations: 1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Animate `top` instead of `transform`

In "The cost board" replace the top row with ["transform", "translateY(0px)", "translateY(6px)"] and compare its track with the old one.

terminal
$ Read the cost mark of the swatch before and after
── what you'll see ──
`top`: layout (red). `transform` with `translateY`: composite (green). The swatch moves the same 6 px.
From the timeline's tracks; the table measured `top` at 61 Layout events in 61 frames and `transform` at 0.

Myth vs fact

Myth

background-color always repaints, so never animate it.

Fact

In the Chromium we measured it was composite only. Check your own browsers with Paint flashing before deciding; it is fine for a button hover.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A property inside a layer of its own repaints only that layer, so the cost of a paint animation is the painted area times the frames.

Remember this

  1. 1

    The problem this solves: "only animate transform and opacity" is a good rule, but incomplete. In current Chromium some properties you were told are expensive are not, and some you thought cheap are not. Measure, then choose.

  2. 2

    The three costs. Composite: the browser only moves, fades or blends layers it already painted. Nothing on the main thread. Paint: the browser draws the element's pixels again each frame, but sizes and positions stay as they are. Layout: the browser works out sizes and positions again, which can move other elements, and then paints.

  3. 3

    How Hectal measured. scripts/dsa-tools/measure-anim-cost.cjs animates one property at a time in Chromium 153, records a one-second trace and counts Layout and Paint events on the main thread. Layout on over a fifth of the frames means "layout", else Paint on over a fifth means "paint", else "composite". The timeline's cost colours use this table.

  4. 4

    Read the table with care. It was measured in headless Chromium with software rendering, not on a phone GPU, and other browsers differ. Treat it as a first guess and measure your own page (Topic 6.5) with Paint flashing.

  5. 5

    Why transform and opacity are cheap. The compositor applies a layer's transform and opacity when it stacks layers, so the layer's pixels are not touched. A shadow or a border colour is part of the pixels, so changing it means drawing them again. The usual fix: paint the effect once and animate only a layer property, such as a shadow on a pseudo-element whose opacity animates.

Explain it without notes

01

What are the three costs of animating a property, and what does each add to a frame?

02

How would you animate a card's shadow without painting every frame?

Practice

01

A button grows a box-shadow on hover. Rewrite it so the hover costs only composite.

02

In "The cost board" add a tenth swatch that animates filter: blur(2px). What colour is its track?

Done when you can

  • You can name the three costs and give two properties for each

  • You can read a track's cost colour on the Motion Timeline

  • You can replace a shadow animation with an opacity animation of a pseudo-element

  • You know the table was measured in headless Chromium and how to check your own browser