リフローとリペイント入門

ブラウザが画面を描き直すしくみを、図とデモで見てみよう。

1. ひとことで言うと?

リフロー(Reflow)

要素の位置や大きさを計算し直すこと。「レイアウト」とも呼ばれます。
🏠 家具の配置換え:ソファを大きくしたら、周りの家具も全部動かして測り直す。

リペイント(Repaint)

形や位置はそのままで見た目(色など)だけ塗り直すこと。
🎨 壁の塗り替え:家具は動かさず、ペンキを塗り直すだけ。

配置換え(リフロー)をすると、そのあと必ず塗り直し(リペイント)も発生します。だからリフローのほうが重い処理です。

2. ブラウザが画面を作る流れ

HTMLとCSSを受け取ったブラウザは、次の順番で画面を作ります。

HTML → DOMツリー CSS → CSSOM ① スタイル計算 どの要素にどのCSSが当たるか ② レイアウト 位置・大きさを計算 ← リフロー ③ ペイント 色・文字・影などを描く ← リペイント ④ コンポジット レイヤーを重ねて表示

ポイントは「上の段からやり直すほど重い」ということ。②からやり直すのがリフロー、③からやり直すのがリペイントです。④だけで済む変更(transform など)がいちばん軽量です。

3. 図で見るリフロー

1つの箱の幅を広げると、後ろにある箱まで押し出されて位置が変わります。ブラウザは全員の位置を計算し直す必要があります。

変更前 A B C D A の width を広げると… A(広くなった) B C D ← B・C・D も位置が変わる (点線 = 再計算された要素)

4. 図で見るリペイント

色を変えるだけなら、大きさも位置も変わりません。その箱を塗り直すだけで済みます。

変更前 A B C A の background-color を変えると… A ← 他は何も変わらない

5. 触ってみよう:どの段階が動く?

ボタンを押すと、太枠の箱(A)のスタイルが変わり、ブラウザがやり直す段階が光ります。

スタイル計算
→
レイアウトリフロー
→
ペイントリペイント
→
コンポジット合成
A
B
C
D
ボタンを押してみてください。

6. どのプロパティで何が起きる?

発生するもの代表的なプロパティ・操作
リフローwidth height margin padding border-width top/left font-size display、要素の追加・削除、テキストの変更、ウィンドウのリサイズ
リペイントcolor background-color background-image border-color box-shadow outline visibility
合成のみtransform opacity(多くのブラウザで、別レイヤーになっている場合)

※ 実際の挙動はブラウザや状況によって少し違います。「大きさ・位置が変わる=リフロー」と覚えておけば大丈夫です。

7. 気をつけたい「強制リフロー」

ブラウザは本来、変更をまとめてから1回だけリフローします。ところが、スタイルを変えた直後に offsetHeight などのサイズを読み取ると、ブラウザは正しい値を返すためにその場でリフローせざるを得ません。これをループの中でやると、何度もリフローが起きてしまいます(レイアウト・スラッシング)。

✕ 書く→読む→書く→読む… 書 ⚡ 書 ⚡ 書 ⚡ ⚡ = 読むたびに強制リフロー(3回) ◯ まとめて読む → まとめて書く 読 読 読 書 書 書 ✓ リフローは最後に1回だけ

✕ 遅い書き方

const boxes = document.querySelectorAll('.box');
for (const box of boxes) {
  // 読む(強制リフロー)→ 書く を繰り返している
  box.style.width = box.offsetWidth + 10 + 'px';
}

◯ 速い書き方

const boxes = document.querySelectorAll('.box');
// ① 先にまとめて読む
const widths = [...boxes].map(box => box.offsetWidth);
// ② あとでまとめて書く
boxes.forEach((box, i) => {
  box.style.width = widths[i] + 10 + 'px';
});

サイズを読み取るとリフローを起こしうるもの:offsetWidth/Height clientWidth/Height scrollTop getBoundingClientRect() getComputedStyle() など。

8. 軽くするためのコツ

まとめ