v148 · origin trial · Performance

Forced Reflow Detector

Identifies forced synchronous layouts by triggering common "reflow-triggering reads" and measuring the styleAndLayoutDuration reported in LoAF entries. Shows how batching with requestAnimationFrame eliminates the cost.

Checking LoAF styleAndLayoutDuration support…

reflow triggers

Each row below is a DOM property that forces the browser to flush pending style + layout before it can return a value. Run the detector to measure how much styleAndLayoutDuration each one costs inside a long frame.

Trigger catalog

  • el.offsetHeight
  • el.offsetWidth
  • el.getBoundingClientRect()
  • el.clientHeight
  • el.scrollTop
  • el.getClientRects()
  • window.getComputedStyle()
  • el.offsetParent
  • el.scrollHeight
  • el.offsetTop

LoAF metrics

styleAndLayout
forced layout
total duration
script time
Values from the most recent LoAF entry. styleAndLayoutDuration is the Chrome 148 addition.
Mode: forced reflows
Reflows detected: 0
Last batched saving:

before / after: the fix pattern

A single forced reflow per iteration can balloon styleAndLayoutDuration. Batching all writes before reads eliminates mid-tick forced flushes.

BEFORE — forced reflow per item
// Interleaved read/write → forces flush each iteration
items.forEach(el => {
  el.style.width = '100px';  // write
  const h = el.offsetHeight; // read ← FLUSH
  el.style.height = h + 'px'; // write
});
AFTER — RAF-batched reads & writes
// Read phase: collect all geometry
const heights = items.map(el => el.offsetHeight);

requestAnimationFrame(() => {
  // Write phase: apply in next frame
  items.forEach((el, i) => {
    el.style.height = heights[i] + 'px';
  });
});

event log

Ready. Click "Run detector" to start.

how it works

A PerformanceObserver watches for long-animation-frame entries. Each "run" pumps 10 forced reflows in a tight loop — reading a layout property immediately after a style mutation — then reads the LoAF entry's new styleAndLayoutDuration field to quantify the cost. The "batched" run separates all writes from all reads, which eliminates intermediate flushes and reduces styleAndLayoutDuration to near-zero.

const obs = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    // Chrome 148: styleAndLayoutDuration is the new breakdown field
    console.log('styleAndLayout:', entry.styleAndLayoutDuration);
    // Also available: entry.renderDuration, entry.scriptDuration
  }
});
obs.observe({ type: 'long-animation-frame', buffered: true });

references

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗