📝 注意
本記事はAIの補助を受けて編集しています。
内容は大規模Webアプリケーションの実務経験に基づいています。
📚 目次
- 0. はじめに
- 1. ブラウザの仕事を減らすとは?
- 2. レイアウト計算を減らす(Reflow 削減)
- 3. ペイントコストを下げる(Repaint 最適化)
- 4. Composite だけで済ませる設計(最重要)
- 5. レイヤー設計と GPU 活用
- 6. DOM 構造とレンダリングコスト
- 7. JavaScript との関係(軽く)
- 8. まとめ
0. はじめに
これまでのシリーズでは、ブラウザの内部動作を段階的に見てきました。
- Part 1: Rendering Pipeline(描画の流れ)
- Part 2: Reflow / Repaint / Composite(コストの違い)
- Part 3: Critical Rendering Path(初期表示が遅くなる理由)
ここまでで分かることは、とてもシンプルです。
ブラウザは想像以上に多くの処理をしている
それにもかかわらず、開発現場ではこう考えがちです。
- 「とりあえず JS を軽くすればいい」
- 「
memo化すれば速くなるはず」
しかし、本質はそこではありません。
本質
❌ 「どうやって速くするか」ではなく
✅ 「ブラウザに余計な仕事をさせないか」
本記事では、描画パフォーマンス最適化の設計思想を解説します。
1. ブラウザの仕事を減らすとは?
ブラウザは 1 フレーム(約 16ms) ごとに、以下の処理を行っています。
- Layout(リフロー):位置・サイズの計算
- Paint(リペイント):ピクセルの描画
- Composite(コンポジット):レイヤーの合成
この処理が 16ms を超えると:
フレーム落ち(jank) が発生し、カクつきの原因になります。
重要な視点
パフォーマンス改善とは:
- ❌ 処理を「速くする」ことではない
- ✅ 処理そのものを減らすこと
基本原則
| NG(避けるべき) | OK(推奨) |
|---|---|
| レイアウトを頻繁に発生させる | 更新をまとめて行う(バッチ化) |
| DOM を何度も細かく更新する | 一括更新する(class 切替など) |
| 画面全体を再描画させる | レイヤーで影響範囲を限定 |
top / left でアニメーション |
transform を使う |
2. レイアウト計算を減らす(Reflow 削減)
Reflow は最もコストが高い
Reflow が発生すると、ブラウザは以下を再計算します:
- DOM 構造
- CSS の適用結果
- 要素の位置・サイズ
影響範囲が広く、最も重い処理
よくある NG:Layout Thrashing
JavaScript で 読み取り(read)と書き込み(write)を交互に行うと、
毎回強制的に Reflow が発生します。
// ❌ 悪い例
element.style.width = '100px'; // write
console.log(element.offsetWidth); // read → 強制リフロー
改善方法
1. 読み取りと書き込みを分離
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 100 + 'px';
element.style.height = height + 100 + 'px';
2. DOM 更新はまとめる
// ❌ バラバラ更新
el.style.width = '100px';
el.style.height = '100px';
// ✅ 一括更新
el.style.cssText = 'width:100px;height:100px;';
3. CSS Containment を使う
.card {
contain: layout;
}
レイアウトの影響範囲を限定できる
3. ペイントコストを下げる(Repaint 最適化)
Repaint は「見た目の再描画」です。
重いプロパティ
box-shadowborder-radiusfiltergradientposition: fixed
広範囲に使うと負荷が大きい
対策
1. レイヤー分離
.animated {
will-change: transform;
}
2. 重い装飾を避ける
/* ❌ */
box-shadow: 0 0 20px rgba(0,0,0,0.5);
/* ✅ */
border-bottom: 1px solid rgba(0,0,0,0.1);
4. Composite だけで済ませる設計(最重要)
理想
Layout も Paint も発生させない
Composite のみで処理
GPU で処理できるプロパティ
transformopacity
NG / OK
/* ❌ */
left: 100px;
/* ✅ */
transform: translateX(100px);
効果
- GPU で処理される
- メインスレッドをブロックしない
- 60fps を維持しやすい
5. レイヤー設計と GPU 活用
ブラウザは レイヤー単位で描画します。
レイヤー化
.animated {
will-change: transform;
}
注意
- レイヤーはメモリを消費する
- 作りすぎると逆効果
6. DOM 構造とレンダリングコスト
DOM が大きいと遅くなる理由
- Layout 計算量が増える
- Paint 範囲が広がる
対策
1. フラットな構造
2. 仮想化(Virtualization)
必要な分だけ DOM を生成
3. コンポーネント分離
再レンダリング範囲を限定
7. JavaScript との関係(軽く)
JavaScript 実行中は描画が止まります。
悪い例
for (let i = 0; i < 1000000000; i++) {}
対策
- requestAnimationFrame
- debounce / throttle
- タスク分割
8. まとめ
Key Takeaways
- パフォーマンスは設計で決まる
- 「速くする」ではなく 「無駄な処理をさせない」
優先順位:
- Composite だけで済ませる
- Paint を減らす
- Layout を減らす
💡 良い設計とは、ブラウザに無駄な仕事をさせないこと
👉 次回予告
[Frontend Performance - Part 5] なぜ UI は止まるのか?JavaScript の実行モデルと Event Loop を理解する
