ブラウザはCSS変更をピクセルに変換するパイプラインを通します。異なる変更が異なる(より多くまたはより少なくコストがかかる)ステージをトリガーします。
text
Style → Layout (reflow) → Paint → Composite
- Layout / reflow — ジオメトリの再計算(位置/サイズ)。 — 1つの要素を変更すると、多くの他の要素の再計算を強制できます。
ブラウザはCSS変更をピクセルに変換するパイプラインを通します。異なる変更が異なる(より多くまたはより少なくコストがかかる)ステージをトリガーします。
Style → Layout (reflow) → Paint → Composite
/* ❌ trigger LAYOUT (reflow) — costly, recalculates geometry every frame */
width, height, top, left, margin, padding, font-size
/* ⚠️ trigger PAINT — repaint pixels */
color, background, box-shadow, border-radius
/* ✅ trigger only COMPOSITE — GPU, no layout/paint */
transform, opacity
これは単一の最も重要なパフォーマンスルールです。width/top/marginではなく、transformとopacityをアニメートする。 layoutプロパティをアニメートするとフレームごとにlayoutが再実行されます(jank)。transformはGPUで合成されます(滑らかな60fps)。
/* ❌ janky */ @keyframes a { to { left: 300px; width: 200px; } }
/* ✅ smooth */ @keyframes b { to { transform: translateX(300px) scaleX(2); } }
.animated { will-change: transform; } /* promotes to its own GPU layer ahead of time */
will-changeはアニメーション予定の要素のみに使用してください — 過度に使用するとレイヤーを多く作成してメモリを浪費します。
// ❌ read-write-read-write forces multiple synchronous reflows
for (const el of els) { el.style.width = el.offsetWidth + 10 + "px"; }
// ✅ batch reads, then writes
const widths = els.map(el => el.offsetWidth);
els.forEach((el, i) => el.style.width = widths[i] + 10 + "px");
- contain: layout/paint — isolate a subtree so changes don't reflow the whole page
- content-visibility: auto — skip rendering offscreen content
- minimize deep selectors and huge unused stylesheets
レンダリングパフォーマンスは主にlayoutを繰り返しトリガーしないことについてです。
transform/opacityがcomposite専用(安い)であり、width/top/marginがreflowを強制する(高い)ことを知ること — さらにlayout thrashingを回避しcontain/content-visibilityを使用することが、アニメーションを60fpsで保ちページを高速に感じさせます。