レイアウト(リフロー)は「ブラウザの重労働」――なぜ我々はそれを避けなければならないのか
やあ。今日もフロントエンドの戦場に立っている諸君、お疲れ様。
画面の中のピクセルを自在に操る我々だが、裏側でブラウザがどれほど必死に計算をしているか、意識したことはあるだろうか?
特に「レイアウト(リフロー)」というプロセスは、ブラウザにとって最もコストのかかる「重労働」の一つだ。今日は、この泥臭い計算の裏側を紐解き、パフォーマンスを最大化するための武器を授けよう。
—
ブラウザの脳内で起きている「幾何学の計算」
DOMツリーとCSSOMツリーが合体して「レンダーツリー」ができた瞬間、ブラウザは安心などしていない。むしろ、ここからが地獄の始まりだ。
レイアウトの仕事は、「すべてのノードが画面のどこに、どのくらいの大きさで存在するのか」を決定すること。親のサイズが子のサイズに影響し、子のサイズがさらに孫のサイズに……という具合に、ブラウザはツリーのトップから再帰的に計算を繰り返す。
例えば、`width: 50%` と書けば、ブラウザは親のコンテナの幅を取得し、それに0.5を掛けて、さらにpaddingやborderの計算を重ねる。これを画面上の数千個のノードすべてに対して行うわけだ。
もし君がJavaScriptで要素のサイズを動的に変えたらどうなるか? ブラウザは「あ、世界のルールが変わった。全部計算し直さないと!」と、リフローの嵐に突入する。これがユーザーの「カクつき」を生む原因だ。
—
「強制同期レイアウト」という罠
現場でよく見る「やってはいけないコード」の典型がこれだ。
// 悪い例:強制同期レイアウト(レイアウトスラッシング)
const box = document.getElementById(‘box’);
// 1. スタイルを変更(汚染)
box.style.width = ‘200px’;
// 2. 直後にサイズを取得(測定)
// ブラウザは「あ、変更が確定してないから再計算しなきゃ」と強制的にリフローを実行する
const width = box.offsetWidth;
// ループの中でこれをやると、ブラウザは何度も何度も計算を強制される。
// これが「レイアウトスラッシング」だ。
ブラウザは賢い。本来なら、JavaScriptの実行が終わるまでリフローを遅延させ(一括処理)、パフォーマンスを最適化しようとする。しかし、`offsetWidth` のような値を読み取ろうとすると、ブラウザは「今すぐ正確な値を出せ!」という命令に従うために、計算を中断してリフローを強要されるのだ。
—
ベストプラクティス:計算を「読み」と「書き」で分ける
この惨事を防ぐためには、「書き(スタイルの変更)」と「読み(サイズの取得)」を明確に分離するのが鉄則だ。
以下は、実務でも使える「読み書き分離」のパターンだ。
// 良い例:読み書きを分離してリフローを最小限に抑える
const box = document.getElementById(‘box’);
// 1. 読み込みフェーズ(変更前に必要な値をキャッシュする)
const currentWidth = box.offsetWidth;
// 2. 書き込みフェーズ(変更をまとめて実行する)
// このあと、ブラウザは次のフレームで一括してレイアウトを計算してくれる
requestAnimationFrame(() => {
box.style.width = `${currentWidth + 100}px`;
box.style.backgroundColor = ‘red’;
});
`requestAnimationFrame` を使うことで、ブラウザが一番計算しやすいタイミング(ブラウザの描画周期)に処理を合わせることができる。これがプロのフロントエンドエンジニアが守るべき「ブラウザへの礼儀」だ。
—
シニアからのアドバイス:レイアウトを「極小化」せよ
最後に、現場で戦う君たちへの実践的なTipsを贈ろう。
1. transformを活用せよ: `top`や`left`を動かすとレイアウトが発生するが、`transform: translate()` ならGPUが担当する「コンポジット」で済むことが多い。レイアウトをトリガーしないCSSプロパティを覚えよう。
2. DOMの深さを制限する: レイアウトの計算コストはDOMの深さに比例する。必要以上に `div` を重ねるな。
3. Containプロパティ: CSSの `contain: layout;` を使うと、その要素の内部での変更が外部に影響しないことをブラウザに教えられる。計算範囲を限定する強力なツールだ。
ブラウザのレンダリングエンジンは、君たちが書いたコードを物理的に再現するための「エンジン」だ。その仕組みを理解すれば、コードを書く際に「これはリフローを引き起こすかな?」と一瞬立ち止まれるようになるはずだ。
パフォーマンスは、こういった小さな積み重ねの先にある。明日からのコーディングで、ぜひ「リフロー」を意識してみてくれ。健闘を祈る。

コメント