Batch DOM reads and writes

Don't read layout between style writes. Batch them or toggle a class.

Paul Irishgist.github.com/paulirish
See this gist and CSS Triggers for more information on layout-forcing operations.
What forces layout / reflowView source ↗
CSS Triggerscsstriggers.com
See this gist and CSS Triggers for more information on layout-forcing operations.
Which properties trigger layout, paint, compositeOpen csstriggers.com ↗

From vercel-react-best-practices, rule js-batch-dom-css.

One layout instead of hundreds

Resize, then double the boxes and resize again.

Write, read, repeatBad
– ms– forced layouts
el.style.width = w; el.offsetWidth per box
Writes, then readsGood
– ms– forced layouts
writes; reads; writes
// 1. every write
boxes.forEach((el, i) => { el.style.width = widths[i] + 'px' })
// 2. every read: the first one lays out, the rest are cached
const rendered = boxes.map((el) => el.offsetWidth)
// 3. every write that depends on a read
boxes.forEach((el, i) => el.classList.toggle('is-wide', rendered[i] >= 20))

Which read forces layout?

Click a step to cycle its type, or pick a preset.

Loop body × 300 boxesSequence
– forced layouts– ms
// Batch writes, then read once
el.style.width = '100px'
el.style.height = '200px'
const { width, height } = el.getBoundingClientRect()

// Or batch reads, then writes
const rect = el.getBoundingClientRect()
el.style.width = rect.width * 2 + 'px'
el.style.height = rect.height * 2 + 'px'

Four writes or one class

Toggle the highlight, then time 500 toggles.

Inline stylesBad
0 style writes0 forced layouts– ms / 500

style=(none)

el.style.width = …; el.offsetWidth; el.style.height = …
One class toggleGood
0 class changes0 forced layouts– ms / 500

class=lt-target

el.classList.toggle('highlighted-box')

.highlighted-box {
  width: 100px;
  height: 200px;
  background-color: blue;
  border: 1px solid black;
}

// React: state picks the class, no effect, no ref
function Box({ isHighlighted }) {
  return <div className={isHighlighted ? 'highlighted-box' : ''}>Content</div>
}

Why it matters, pros and cons

Why it matters

A read after a write forces layout now. In a loop, that's once per element.

Pros

  • One layout instead of one per element.
  • Classes cache, theme, and undo in one call.
  • React: derive className, skip the effect.

Cons

  • Reads like dataset or textContent are already free.
  • One measure per interaction costs a layout already coming.
  • Runtime values (drag offset) need a CSS variable, not a class.