レイアウトスラッシング:1フレーム内で繰り返されるリフローと止め方
何が起きているか
ブラウザはフレームごとに1回レイアウトを再計算しようとします。しかしコードがスタイルを書き、次にジオメトリ(offsetWidth、getBoundingClientRect)を読み、また書く — をループで繰り返すと、読み取りのたびにページの新しい同期レイアウトが強制されます。100要素のループは1フレームで100回の完全なリフローを引き起こすことがあります。
コストはDOMサイズに比例するため、小さなページでは問題ない同じコードが、数千ノードのページではINPを破壊します。
見分け方
- INPの内訳でpresentation delay(ハンドラー後のレンダリング作業)が支配的。
- DevTools Performanceで1フレーム内に多数の紫のLayoutブロックが見え、しばしば「Forced reflow」と表示される。
- コードがループ内で読み取り(offsetTop、clientHeight、getComputedStyle)と書き込み(style、classList)を交互に行っている。
直し方
- まず全部読み取り、次に全部書き込む — ループ内で交互にしない。
- 書き込みをrequestAnimationFrameに移し、次の描画の直前に反映されるようにする。
- 要素をJSで測って配置するより、CSSの解決策(クラス、transform、flex/grid)を優先する。
問題
items.forEach((el) => {
el.style.width = base + "px" // write
const h = el.offsetHeight // read → forced reflow, every iteration
el.style.height = h * 2 + "px" // write
})
修正
const heights = items.map((el) => el.offsetHeight) // all reads
requestAnimationFrame(() => {
items.forEach((el, i) => { // all writes
el.style.width = base + "px"
el.style.height = heights[i] * 2 + "px"
})
})
サイトを無料でスキャン →