ブラウザレンダリングのプロセスを理解する

# はじめに

ウェブページをブラウザで表示する際、背後では複雑なレンダリングプロセスが実行されています。
これまで、レンダリング工程を深くまで理解せず（言わばブラックボックス状態）に開発を進めていたので、この機会に改めて整理したいと思います。
この記事では、レンダリングプロセスの主要な工程について詳しく解説していきたいと思います。

# 関連記事
本記事に関連した記事も投稿したので、ぜひ読んでいただけると幸いです！

- [ブラウザのリソース読み込みを最適化するためのパフォーマンス向上テクニック](https://qiita.com/fujisho1216/items/d53443c6eb5e2a8acc34)
- [レンダリングプロセスにおける Scripting の高速化テクニック](https://qiita.com/fujisho1216/items/5a3e16cfd3c22c77f8ea)
- [CSSスタイル計算とレイアウトの効率化](https://qiita.com/fujisho1216/items/981292adec80f787ad3a)

# 参考書
[Webフロントエンド ハイパフォーマンス チューニング](https://www.amazon.co.jp/Web%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89-%E3%83%8F%E3%82%A4%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9-%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0-%E4%B9%85%E4%BF%9D%E7%94%B0-%E5%85%89%E5%89%87/dp/4774189677)

# レンダリングの大まかな流れ

以下の4つの工程からなるレンダリングが始まって、最終的に描画されるまでを、**フレーム（ Frame ）** と呼びます。

1. **Loading**
2. **Scripting**
3. **Rendering**
4. **Painting**

## 各工程で行われる処理

各工程では、細かいレンダリングまでフェーズと呼ばれる処理が走っています。

### Loading

- **Download**
    - リソースのダウンロード
- **Parse**
    - HTMLやCSSの解析

### **Scripting**

- JavaScriptの実行

### Rendering

- **Calculate Style**
    - CSSを適用してスタイルを計算
- **Layout**
    - レイアウトの計算

### Painting

- **Paint**
    - 要素の描画
- **Rasterize**
    - ピクセルデータの生成
- **Composite Layers**
    - レイヤーの合成

# レンダリングの工程の詳細

## 1. Loading（ロード） - リソース読み込み

ロードは、ブラウザに与えられた URL から HTML を読み込んで、そこからさらにレンダリングに必要な付属するリソースを読み込んで解釈していきます。

### Download（ダウンロード）

レンダリングプロセスの最初のステップは、ウェブページに関連するリソースのダウンロードです。
これには、HTML ドキュメント、CSS ファイル、JavaScript ファイル、画像、フォントなどが含まれます。
ブラウザは、サーバーとの通信（ HTTP 通信... etc ）を行い、これらのリソースをネットワーク経由で取得します。
HTTPリクエストが送信され、サーバーからのレスポンスが返ってくることで、リソースがブラウザに届きます。

### Parse（パース）

ダウンロードされた HTML や CSS を解析します。
これは、すぐにブラウザの内部で行われます。

- HTML
    - Document Object Model（ DOM ）ツリーに変換
- CSS
    - CSS Object Model（ CSSOM ）ツリーに変換

この段階で、 DOM ツリーと CSSOM ツリーが結びつき、レンダリングツリーが構築されます。

#### より詳細な DOM ツリーへの変換工程

1. 字句解析によるトークンのリスト化
2. 構文解析による構文木の構築
3. 構文木内にある JavaScript を実行しつつ DOM ツリーの構築

---

### 各単語の補足

#### トークンとは

意味的に 1 つの塊になっている文字列です。

#### 構文木とは

トークンの列を解析することで生まれる木構造のデータです。

#### JavaScript の同期実行

 HTML の中に JS （ script タグ）が含まれている場合は、 HTML を DOM ツリーに変換する過程の中でJS を実行しなければなりません。
そのため、DOM ツリーがJS の実行結果に依存し、その実行を待たなければならない（同期実行）ので、パフォーマンスの観点で注意が必要です。

## 2. Scripting（スクリプティング）

スクリプティングは、リソース一式を読み込んだ後、 JS 実行を行います。

### JS 実行の流れ

1. 字句解析
    1. トークン列（ラベル付けをした文字列のリスト）に変換
2. 構文解析
    1. 抽象構文木( JS の文法に則った形で表現される木構造のデータ)に変換
3. コンパイル
    1. 抽象構文木を実行可能な形式にコンパイルする（ブラウザの JS エンジンの実装に依存する）
        1. 処理内部の仮想マシン用コードへの変換
        2. JIT コンパイルによる機械語への変換
        3. 用法(トレーシング JIT )
4. 実行
    1. 処理内部の仮想マシン、もしくは CPU で実行
        1. DOM API を通じた DOM ツリーの操作により後続の Rendaring フェーズや Painting フェーズを再度引き起こす

### 具体例

1. **JavaScriptコード**
    - `console.log("Hello World!");`
2. 字句解析(トークン列に変換)
    - **Identifier**
        - `console`
    - **Operator**:
        - `.`
    - **Identifier**
        - `log`
    - **Parenthesis**
        - `(`
    - **String**
        - `"Hello World"`
    - **Parenthesis**
        - `)`
    - **Separator**
        - `;`
3. 構文解析(抽象構文木に変換)
    - **Call Expression**
        - **Member Expression**
            - **Identifier**
                - `console`
            - **Identifier**
                - `log`
        - **String**
            - `"Hello World"`

---

### 各単語の補足

#### 処理内部の仮想マシン用コードへの変換

プログラムを仮想マシンが理解できる中間コード（バイトコードなど）に変換し、それを仮想マシンが実行する方法です。

#### JIT コンパイルによる機械語への変換

実行時にプログラムを直接機械語にコンパイルして実行する方法です。

#### 両方（トレーシング JIT ）

プログラムの特定の実行パスを記録し、その部分だけを最適化して機械語にコンパイルする技術です。

#### 仮想マシン（ JS エンジン）

仮想マシンは、物理ハードウェア上で動作する仮想的なコンピュータ環境のことで、ソフトウェア的に実装されたコンピュータです。

仮想マシン上では、オペレーティングシステムやアプリケーションを実行できます。

そのため、当たり前にご存知だとは思いますが、アプリケーションを仮想化する Docker とは別物です。

## 3. Rendering（レンダリング）

レンダリングは、構築されたレンダリングツリーを基に、ウェブページのスタイルの計算とテイアウトの処理を行い、実際の描画を行います。

### Calculate Style（スタイルの計算）

まず、 CSSOM ツリーを基に、各要素に適用される最終的なスタイルを計算します。
スタイル計算では、継承やカスケードのルールが適用され、フォントカラー、フォントサイズ、余白などが決定されます。
この結果が、次のレイアウト計算（後続の Layout ）に使用されます。

#### 詳細な内部的な処理

下記の流れで処理が走ります。
1. CSS ルールのマッチング処理
1. CSSセレクタのマッチング
1. 適用される CSS プロパティの算出


##### CSS ルールのマッチング処理

 CSSOM ツリーから CSS ルールセットを走査し、 DOMツリー から各要素に適用される CSS ルールを計算します。

この処理により、どのDOM要素にどのスタイルが適用されるかが決定されます。

##### CSSセレクタのマッチング

CSS セレクタのマッチングは通常、セレクタの右側（ターゲット要素）から行われます。

例えば、子孫セレクタや子セレクタを使用する際、指定された要素に対して最も右側のセレクタから順に一致を確認し、スタイルが適用されます。

つまり、下記のようなコードがあった場合、次のようなアサートを順に処理することで実行されます。

この確認が全て失敗せずに終わればマッチングは成功します。

```jsx
body > .container > .button {
  ...
}
```

1. DOM 要素の class 属性に button が含まれている
2. その親要素の class 属性に container が含まれている
3. その要素の DOM 要素名が body である

##### 適用される CSS プロパティの算出

 CSS セレクタのマッチング処理が終わり、どの DOM 要素に対してどの CSS ルールセットが適合するかレンダリングエンジンにはわかるようになりました。

ここでは、 DOM 要素にどの CSS プロパティと値が適用されるのか算出します。

詳しくは省きますが、CSS ルールセットの概念である詳細度を踏まえて、詳細度計算を行い、 DOM 要素に対して適用される CSS プロパティが算出されます。

### Layout（レイアウト）

 DOM ツリー内すべての DOM 要素に当たる CSS プロパティを算出した後、レンダリングエンジンは DOM ツリー内のすべてのノードの視覚的なレイアウト情報の計算、レイアウト（ Layout ）を行います。

#### レイアウト情報

- 要素の大きさ
  - 各要素の幅や高さを計算します
  - これには、 width や height プロパティだけでなく、コンテンツのサイズやボックスモデルの他の要素（例えばborderやpadding）も考慮されます
- 要素のマージン
    - 要素間の外側のスペースであるマージンがどれだけあるかを計算します
- 要素のパディング
    - 要素の内容とその境界線との間のスペースであるパディングを計算します
- 要素の位置
    - 要素がページ内でどの位置に配置されるかを決定します
    - これには、静的なドキュメントフロー（ static ）、相対配置（ relative ）、絶対配置（ absolute ）、固定配置（ fixed ）などのCSSプロパティが影響します
- 要素の z 軸の位置
    - 要素がページ上の他の要素と重なる際の表示順序を決定します

ここまでで、HTML と CSS をそれぞれツリーに変換し、それをレイアウトツリーに変換（DOM要素に当たるCSSプロパティを算出）が完了しました。

次に、実際の描画です。

## 4. Painting（ペインティング）

ペインティングプロセスでは、レイアウトで決定された位置とサイズに基づき、最後のレンダリング結果の描画を行います。

このフェーズでようやく、レンダリングエンジンはユーザーが見ることのできる実際のピクセルを描画します。

### Paint（ペイント）

内部の低レベルな 2D グラフィックエンジン向けの命令を生成します。

### Rasterize（ラスタライズ）

生成された命令を用いて実際にピクセルへ描画します。

この時、レイヤーという単位で一枚一枚描画されます。

### Composite Layers（レイヤーの合成）

最後に、複数のレイヤーが合成され、最終的な表示が作成されます。

この合成処理には、 CPU と GPU の両方が利用されます。

通常、レイヤーは CPU で合成されますが、特定の条件を満たすと GPU が利用されます。

例えば、 CSS の 3D 変形プロパティ（例: `translate3d()`）を使用すると、その要素が GPU で合成されるレイヤーとなります。

---

### 各単語の補足

#### レイヤーとは

オーバーラップ（重なって）表示されるコンテンツがある場合、 x 軸の上下関係が考慮されて合成されます。

例えば下記です。

- 要素が position: absolute なスタイルプロパティとして適用される
- 要素に opacity CSS プロパティが適用されて、透過して背後のコンテンツが表示される

利点としては、再レンダリングする場合、すでに描画が終わったレイヤーを再利用することで、素早くサイレンダリングできる場合があります。

# 再レンダリング
URL を与えられたレンダリングエンジンがコンテンツを表示できました。

ただ、初期読み込みが終わって描画し終わってからも、ユーザーやブラウザの何らかのアクションや JS の実行によってレンダリングは再度引き起こされます。

これを再レンダリングと呼びます。

注意点として、これまでのすべての処理を最初からやり直すわけではありません。

多くのレンダリングエンジンは、これまで描画のために構築した内部表現のオブジェクトを再利用しようとします。

例えば、JS で DOM ツリー内の DOM 操作をすると、レイアウトが変更されるので、再度 Layout（場合によっては Rendering 全体）が引き起こされます。

つまり、再レンダリングでは、1 部のフェーズのみが再実行される場合もあれば、複数のフェーズが実行される場合もあります。

JS でどのようなコードを書くとレンダリングのどのフェーズが引き起こされるのかを把握しておくと、パフォーマンスの優れたコード / アプリケーションを作成することができます。

# まとめ
レンダリング / 再レンダリング時のフローを理解しておくだけで、開発者ツールの performance タブの活用方法の幅が広がると思います。
パフォーマンス改善を実施する場合は、今回のレンダリング工程を念頭に置いて、ボトルネックとなっている箇所の特定を行いたいです。