【いらすとや図解】はじめてのレンダリング
この記事に関して
ブラウザのレンダリングについて書いていきます。
細かい仕様とかは抜きにしてざっくり概要を書いていきますのでわかりやすくなっているかなと思います。
ブラウザのレンダリングから、具体的な問題への対処法、将来を見越した設計の基本などを今後書いていく予定です。
この記事は下記の本をベースとして書いています
この記事の対象者
- レンダリングってなんですか?という方
- フロントエンドのパフォーマンスになんとなく意識(興味)を持った方
この記事で取り扱わないこと
- React.jsやVue.jsといったフレームワーク特有のレンダリング
- 細かい仕様等に関して(わかりやすさを重視しているため)
関連記事
(準備中)
レンダリングの流れに関して
ブラウザにURLを打ち込んでから、ウェブページが表示されるまでにどういった超ざっくり説明すると以下の4つの工程になります。これがレンダリングと呼ばれるものです
- Loading(リソースの読み込み)
- Scripting(JavaScript実行)
- Rendering(レイアウトツリー構築)
- Paining(レンダリング結果の描画)
Loading(リソースの読み込み)
ユーザーが指定したURLを使ってHTMLを読み込んで、そこからさらにレンダリングに必要な関連リソースを読み込んで解釈していきます。
ここでいうリソースとはHTML、CSS、JavaScript、画像ファイルなどがあります
リソースの取得
ブラウザは、ユーザーが指定したURLを使ってサーバーにHTTPリクエストを送信し、HTMLファイルを取得します。取得したHTMLファイルの中に含まれるCSSやJavaScript、画像などのリソースも順次リクエストして取得します。
リソースのパース(DOMツリーの構築)
取得したリソースは、ブラウザのレンダリングエンジン[1]によって内部的にパース(解析)されます。HTMLはDOMツリー(Document Object Modelツリー)、CSSはCSSOMツリー(CSS Object Modelツリー)として変換されます。
これにより、HTMLの構造が木構造のデータ(DOMツリー)として表現され、CSSのスタイル情報も別のツリー(CSSOMツリー)として扱われます。これはブラウザがページを表示するための基盤となります。
Scripting(Javascript実行)
レンダリングエンジンは、JavaScriptのコードをJavaScriptエンジンに渡して実行させます。
JavaScriptエンジンは、コードを実行可能な形に変換してから実行します。この変換プロセスは次のように進みます。
1. コードの解析: コードを細かい部分(トークン)に分ける
2. 抽象構文木の作成: トークンを使ってコードの構造を木の形で表す(抽象構文木)
3. コンパイル: 抽象構文木を実行できる形式に変換する
これで、JavaScriptのコードが実行されます。
また、ユーザーの操作やイベントが発生すると、それに応じてJavaScriptが実行されます。
Rendering(レイアウトツリー構築)
JavaScriptの実行が終わると、次にレンダリングエンジンがレイアウトツリーを構築する作業(Rendering)に移ります。この段階では、スタイルの計算(CalculateStyle)とレイアウト(Layout)の2つの処理が行われます。
サンプルコード
<body>
<div class="container">
<button class="button primary">Click Me</button>
</div>
</body>
body {
background-color: white;
}
.container {
padding: 20px;
}
.button {
font-size: 14px;
color: black;
}
.button.primary {
color: white;
background-color: blue;
}
スタイルの計算(CalculateStyle)
スタイルの計算は以下の手順で行われます。
1: セレクタのマッチング
最初に、レンダリングエンジンは各DOM要素に対して適用可能なCSSルールを見つけるため、CSSセレクタをマッチングします。例えば、<button class="button primary">
要素には以下のセレクタがマッチします
- .button
font-size: 14px; color: black;
- .button.primary
color: white; background-color: blue;
2: 詳細度の計算
マッチしたCSSルールの中で、競合するプロパティ(この例ではcolor)について詳細度を比較します。
.button.primary
のセレクタの詳細度は (0, 1, 1)、.button
の詳細度は (0, 0, 1) です。
セレクタの詳細度とは以下の計算方法です。詳細度は3つの数字の組み合わせで表されます。
- 最初の数字: IDセレクタの数
- 2つ目の数字: クラスセレクタ、属性セレクタ、擬似クラスの数
- 3つ目の数字: 要素セレクタや擬似要素の数
.button.primary {
color: white;
}
詳細度は (0, 2, 0)。以下詳細
- IDセレクタの数: 0(IDセレクタは使っていない)
- クラスセレクタの数: 2(.buttonと.primary)
- 要素セレクタの数: 0(要素セレクタは使っていない)
.button {
color: black;
}
詳細度は (0, 1, 0)。以下詳細
- IDセレクタの数: 0
- クラスセレクタの数: 1(.button)
- 要素セレクタの数: 0
.button.primary
の詳細度 (0, 2, 0) は、 .button
の詳細度 (0, 1, 0) よりも高いです。
したがって、.button.primary
のルールが優先され、color: white;
が適用されます。
このように、詳細度は数の大きい方が強く適用されるという考え方です。
レンダリングエンジンは、各DOM要素に最終的に適用されるスタイルを決定します。
button 要素に対して適用されるスタイルは以下の通りです。
- font-size: 14px;
- color: white;(.button.primaryから適用)
- background-color: blue;(.button.primaryから適用)
レイアウト(Layout)
ブラウザがHTMLやCSSを読み込んだ後、レンダリングエンジンはDOMツリー内のすべての要素に対して、どのように表示するかを計算します。これが「レイアウト(Layout)」と呼ばれる処理です。具体的には、以下のような情報が計算されます。
- 要素の大きさ(幅や高さ)
- 要素のマージン(周囲の余白)
- 要素のパディング(コンテンツと境界線の間の余白)
- 要素の位置(ページ内での配置)
- 要素のz軸の位置(他の要素との前後関係)
Paining(レンダリング結果の描画)
最後に行われるのが「描画(Painting)」のフェーズで、この段階でブラウザがユーザーに見えるピクセルを画面に描画します。描画フェーズは以下の3つの処理で構成されています。
-
Paint(ペイント): 画面に描くための下準備
-
Rasterize(ラスタライズ): 下準備された内容をもとに、実際の画面にピクセルを置いていきます。場合によっては、いくつかの層に分けて描きます
-
Composite Layers(レイヤーの合成): 層を重ねて、最終的な画像を作ります。特定の条件で、GPUがこの処理をして描画を速くします
ブラウザはページを最初に描画した後も、ユーザーの操作やJavaScriptの動きによって再度描画を行います。ただし、その際に全てを最初からやり直すわけではなく、必要な部分だけを効率的に更新します。
再描画の仕組みを理解することで、より速く動作するコードを書くことができます。
終わりに
今回はレンダリングに関して書きました。
この記事がきっかけでReact.js等のレンダリングの理解に挑戦しやすくなれば幸いです。
最後までお読みいただきありがとうございます!
-
ウェブページを表示するためにブラウザ内部で動作する仕組み。例えば、GoogleのBlinkなど。レンダリングエンジンは、HTML、CSS、画像などの要素を読み込み、それを画面に表示する役割を担っている。つまり、レンダリングエンジンはウェブページのコンテンツを描画する専門家であり、ブラウザのメニューやボタンなどの見た目や操作感は扱ってない。 ↩︎
Discussion