こんにちは!フロントエンド・アーキテクチャの現場を長年歩んできた者です。
Web制作を学び始めると、「画面がカクカクする」「スマホでスクロールすると妙に重い」という、言い知れぬパフォーマンスの壁にぶつかりますよね。その原因の多くは、今回取り上げる「リフロー(再レイアウト)」という、ブラウザの隠れたお仕事にあります。
「なんだか難しそう……」「コードがうまく動かなかったらどうしよう」と不安になるかもしれませんが、大丈夫ですよ。今回は、ブラウザの頭の中をのぞき見するような気持ちで、身近な例え話と一緒に優しく解きほぐしていきましょう。
—
そもそも「リフロー」ってなに? お引越しの例え話
ブラウザがWebページを表示する仕組みは、よく「大きなお引越し」に例えられます。
1. HTMLのパース(段ボールの荷解き): 届いた荷物を一つずつ出して、「これは椅子、これは本棚」と識別していきます。
2. DOM / CSSOMツリー構築(お部屋のレイアウト図作り): 「この家具はどこに置いて、何色のペンキを塗るか」という設計図を組み立てます。
3. リフロー(レイアウト): 設計図をもとに、「このテーブルの幅は〇センチだから、壁からこれくらい離して……」と、すべての家具の正確な位置やサイズを計算し直す作業です。
4. リパイン / ペイント(色塗り・配置): 最後にペンキを塗り、画面にピクセルを描き出します。
この中で、「リフロー」は家具の位置や大きさをミリ単位で測り直す、ものすごく頭を使う大仕事です。
JavaScriptを使って「ねえ、この要素の今の正確な幅を教えて!」とブラウザに尋ねたり、大きさを変えたりすると、ブラウザは「うわっ、急に模様替えを頼まれた! 全体の位置を再計算しなきゃ!」と慌ててリフローを始めてしまいます。これが、ブラウザを疲れさせ、画面をカクつかせる原因になるのです。
—
要注意! 触るとリフローを引き起こす「危険なプロパティとメソッド」たち
JavaScriptから以下のプロパティやメソッドにアクセス(読み書き)すると、ブラウザは「今の正確なサイズや位置を知りたいんだな」と察知し、強制的にリフローを発生させます。
1. 要素の「位置やサイズ」を測定するプロパティ(通称:強制同期レイアウトの常習犯)
これらをJSで読み取ろうとした瞬間、ブラウザは「嘘をついちゃいけない」と、その場で計算をやり直します。
- `element.offsetWidth` / `element.offsetHeight`(要素の幅と高さ)
- `element.offsetLeft` / `element.offsetTop`(画面上の位置)
- `element.getBoundingClientRect()`(要素の正確な位置とサイズを丸ごと取得)
2. スクロール関連のプロパティ
画面のスクロール位置を尋ねる時も、ブラウザはレイアウトが確定していないと答えられないため、リフローが起きます。
- `window.scrollY` / `window.scrollX`
- `element.scrollTop` / `element.scrollLeft`
3. スタイルを「書き換えた直後」に「読み取る」コード(最悪のコンボ)
これが一番やりがちな罠です。
// 【やってしまいがちなNGパターン】
const box = document.getElementById(‘my-box’);
// 幅を変える(スタイル変更)
box.style.width = ‘100px’;
// すぐに幅を読み取る(ここで強制リフローが発生!)
console.log(box.offsetWidth);
これを連続で行うと、ブラウザは「サイズ変更」→「リフロー計算」→「サイズ取得」を何度も繰り返すことになり、まるで終わらない引越し作業のようにパフォーマンスがガタ落ちします。これを「レイアウト・スラッシング(泥沼のレイアウト)」と呼びます。
—
どうやって防ぐの? 現場で使えるスマートな回避策
「じゃあ、要素のサイズを測ったり変えたりしちゃいけないの?」と思われるかもしれませんが、そんなことはありません。要は「ブラウザに無駄な計算をさせなければいい」のです。
具体的な回避策を見ていきましょう。
回避策1:「読み込み」と「書き込み」のタイミングを分ける(バッチ処理)
ブラウザに仕事を頼むときは、「まとめて書いて、まとめて読む」のが鉄則です。
// 【安心なパターンの例】
const box = document.getElementById(‘my-box’);
// 1. まず「書き込み」をまとめて行う
box.style.width = ‘100px’;
box.style.height = ‘200px’;
// 2. その後で「読み取り」をまとめて行う
// (このタイミングでリフローは1回だけに抑えられます)
const currentWidth = box.offsetWidth;
const currentHeight = box.offsetHeight;
console.log(`幅: ${currentWidth}, 高さ: ${currentHeight}`);
回避策2:CSSのクラス(Class)の付け外しで済ませる
JavaScriptで一つひとつ細かいスタイルをいじるのではなく、あらかじめCSS側でアニメーションやレイアウト変化のクラスを用意しておき、JS側ではクラスを`toggle`(切り替え)するだけにするのが現代のWeb制作の基本です。
// 【CSSクラスを使ったスマートな方法】
const box = document.getElementById(‘my-box’);
// クラスをつけるだけで、ブラウザがよしなに最適化してくれます
box.classList.add(‘is-expanded’);
CSSに任せることで、ブラウザは内部の専用エンジンを使ってGPU(グラフィックボード)をフル活用し、リフローを最小限に抑えて滑らかに動かしてくれます。
—
まとめ
Webブラウザは、私たちが裏側の仕組みを意識しなくても動いてくれる優秀な相棒ですが、あまりに無理な注文(強制的なリフローの連打)をすると、どうしても息切れしてしまいます。
- 「位置やサイズを測るプロパティを触ると、リフローが起きるんだな」
- 「読み取りと書き込みをごちゃ混ぜにせず、整理してコードを書こう」
この2つを頭の片隅に置いておくだけで、あなたの書くコードは見違えるほど軽やかになり、ユーザーにとっても心地よい滑らかなWebサイトが作れるようになりますよ。
最初は気をつけるポイントが多くて大変に感じるかもしれませんが、少しずつ慣れていけば大丈夫です。一緒に一歩ずつ、フロントエンドの奥深い世界を楽しんでいきましょう!

コメント