Webエンジニアが知っておくべきブラウザレンダリングの仕組み

Webサイトの構築を考える上で、ブラウザがどのようにしてレンダリングを行なっているかを理解することは重要です。

ブラウザレンダリングの流れを把握することで、自分の書いたCSSやJavaScriptがどのように読み込まれ、解釈されるかが理解できるようになります。

今回は普段デザインやコーディングをするときはなかなか意識しづらいレンダリングエンジンの流れを見ていきます。

レンダリングの流れ

rendering

ブラウザのレンダリングは上図のような流れで行われます。

Loading, Scripting, Rendering, Paintingの4つの工程からなり、最終的に画面に描画されるまでをフレームと呼びます。

フレーム内の処理を詳しく見るとDownload, Parse, Scripting, Calculate Style, Layout, Paint, Rasterize, Composite Layersと言った処理が行われています。

Loading – リソースを読み込む

最初に行われるのがLoadingと呼ばれる、リソース読み込みの処理になります。

URLが与えられると、ブラウザは画面のレンダリングに必要なリソースを読み込み解釈を始めます。

このフェーズでは、次の処理が主に行われます。

  • リソースのダウンロード
  • リソースのパース(構文解析)

リソースのダウンロード(Download)

ここでのリソースとはHTMLやCSS、JavaScriptファイル、画像ファイルなどがあたります。

リソースの取得ではアプリケーション層ではHTTP、セッション層はTLS、トランスポート層ではTCP、ネットワーク層ではIPなどのプロトコルが使用されます。

セキュアな通信を行いたい場合や高信頼性のネットワークを構築したい場合はオーバーヘッドが発生するため、何を優先したいかを明確にしてネットワークプロトコルを選択することが重要です。

リソースのパース(Parse)

パースではHTMLやCSSをDOMツリーやCSSOMツリーへと変換します。

HTMLの読み込み

HTMLを解釈するとDOMツリーを構築することができます。

DOM(Document Object Model)とは、HTMLのドキュメントを表現するオブジェクトであり、木構造で表されるものです。

HTMLはまず最初に字句解析によりトークン(1つの意味を持つ文字列)がリスト化され、構文解析により構文木が構築、最後に構文木内のJavaScriptを実行しながらDOMツリーが作られます。

この変換過程でツリー内に含まれる画像やCSSなどのリソースの取得や読み込みが行われ、最終的に作られたDOMツリーはJavaScriptなどから操作することができます(DOM操作)。

CSSの読み込み

HTMLで記述されたドキュメントの内容を修飾する言語がCSSですが、ブラウザはlink要素を用いた外部CSSファイルの宣言やstyle要素の内部に埋め込まれたCSSを読み込み解釈を行います。

CSSOM(CSS Object Model)はレンダリングエンジンの木構造の内部表現であり、後々LayoutやPaintingで利用されます。CSSOMもDOMと同様にJavaScriptでアクセスできるようになっています。

 

Scripting – JavaScriptの実行

リソースを読み込んだ後は、JavaScriptの実行を行います。

Blinkなどのレンダリングエンジンは、JavaScriptコードをV8などのJavaScriptエンジンに渡して実行させます。

コードは次のような過程を経て実行されます。

  1. 字句解析
  2. 構文解析
  3. コンパイル
  4. 実行

JavaScriptエンジンに多いのがJIT(Just In Time)コンパイルであり、これはスクリプトエンジンがコード実行時にリアルタイムでコンパイルするものです。JITコンパイルには、実行速度とプラットフォームに依存しないというメリットがあります。

最終的に実行可能な形式にコンパイルされたJavaScriptコードは、処理系内部の仮想マシンやCPUで実行されます。

DOMツリーを操作するとRenderingやPaintingを引き起こし、Ajaxなどを利用して外部リソースを取得した場合には再度Loadingを引き起こします。

 

Rendering – レイアウトツリー構築

JavaScriptの実行が終わった後は、レイアウトツリー構築を行います。

ここでは次の処理が行われます。

  • スタイルの計算
  • レイアウト

 スタイルの計算(Calculate Style)

スタイルの計算では、ドキュメントにスタイルを割り当てるための計算を行います。

CSSOMツリーをもとに、全てのDOM要素に対してCSSセレクタがマッチングするかを総当たりで試した後、CSSルールの詳細を算出し、どのようなCSSプロパティが適用されるかを判断します。

CSSセレクタのマッチング

CSSルールのマッチング処理は、単純計算だとドキュメントにDOM要素が100個あり、CSSルールセットが50個存在した場合には100 * 50 = 5000回のマッチング処理が行われることになります。

body > .container > .button {
...
}

CSSセレクタのマッチングは、上のようなセレクタがあった場合には、直感的には左から順に解釈されるように思えますが、実際にはレンダリングエンジンは右から左に解釈を行います。具体的には次のような処理を行います。

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

CSSプロパティの算出

CSSセレクタのマッチング処理が終わった後は、どのCSSプロパティと値が適用されるかを計算する必要があります。

CSSルールセットには詳細度という概念があり、DOM要素に異なる値の同一のCSSプロパティが与えられた時にどのルールセットが適用されるかを判断するものです。

セレクタの詳細度は3段階あり、ここではa,b,cとして、a > b > cで値の大きいものが優先されます。詳細度は次のように計算されます。

  1. idセレクタの数がaとなる
  2. classセレクタ、属性セレクタ(a[title]など)、擬似セレクタ(:hoverなど)の数がbとなる
  3. 要素セレクタ(divなど)と擬似要素セレクタ(::afterなど)の数がcとなる
  4. 全称セレクタ(*)は無視される

例えば#hogeの場合はa=1 b=0 c=0,li .foo .barの場合はa=0 b=2 c=1のようになります。

例外として!importantをCSSプロパティに付加することで特定のプロパティを優先させることができます。

レイアウト(Layout)

CSSプロパティを計算した後は、レンダリングエンジンはDOMツリー内の要素の視覚的なレイアウト情報を計算します。レイアウト情報とは次のようなものです。

  • 要素の大きさ
  • マージン
  • パディング
  • 位置
  • z軸の位置

 

Painting – 結果の描画

レイアウト情報の算出が終わると、レンダリングエンジンはレンダリング結果の描画を行います。

ここで実際のピクセルを描画、これによりユーザーはコンテンツを見ることができるようになります。

Paintingで行われる処理は次の3つです。

  • ペイント(Paint)
  • ラスタライズ(Rasterize)
  • レイヤー合成(Comosite Layers)

ペイント(Paint)

ここでは、内部の2Dグラフィックエンジン向けの命令を生成します。

グラフィックエンジンはブラウザの実装ごとに異なり、Webkitではプラットフォームやブラウザの実装によって異なるグラフィックエンジンが組み込めるように設計されており、BlinkではSkiaというGoogleが提供するグラフィックエンジンが組み込まれ、SafariではCoreGraphicsというmacOSの提供するAPIがグラフィックエンジンとして組み込まれています。SkiaはMozilla Firefox、Chrome OS、Firefox OSなどでも利用されています。

ラスタライズ(Rasterize)

ペイントで生成された命令を元に実際にピクセルへと描画する処理がラスタライズです。

描画されるときは、レイヤーという単位で1枚ずつ描画されます。レイヤーはオーバーラップして表示されるコンテンツがある場合に生成され、z軸の上下関係を持ちます。レイヤーが生成されるのは次のような条件の場合です。

  • 要素がposition: absoluteなプロパティが適用されている
  • 要素がposition: fixed なプロパティが適用されている
  • 要素がtransform: translate3d(...)と言ったGPUで描画・合成が行われるプロパティを持っている
  • 要素にopacityプロパティが適用されていて背後のコンテンツが透過して表示される必要がある。

こう言ったレイヤーという単位でピクセルに描画することで、再レンダリングする場合にすでに描画が終わったレイヤーを再利用することで効率的にレンダリングを行うことができます。

レイヤー合成(Composite Layers)

ここでは、生成したレイヤーを合成して最終的なレンダリング結果を生成します。

この合成処理には、CPUにより合成とGPUによる合成があり、基本的にはCPUでの生成ですが、ある条件を満たすことによってGPUで合成されるようになります。

例としては、transformプロパティに3d変形関数を指定すると言ったことで、その要素の範囲のレイヤーが生成されてGPUによって合成されるようになります。

アニメーションの描画などは意図的にGPUでの合成を行うことで高速化を測ることができますが、必ずしもGPUでの合成が良いという訳でもないので注意が必要です。

 

再レンダリング

ここまででレンダリングエンジンによるコンテンツの描画が終わった訳ですが、ユーザーやブラウザ側のアクションやJavaScriptの実行、ドキュメント内のイベントによりレンダリングが再度引き起こされる可能性があります。

ただし、全てのレンダリング処理が再度行われるという訳ではなく、一部のフェーズのみが再実行される場合もあります。

例えばJavaScriptのコードが実行されるとDOM操作が行われ、再度Layoutが引き起こされると言ったことす。

この場合はJavaScriptのコーディングを行う際にレンダリングのフェーズを意識しながらコーディングを行うなどの工夫でパフォーマンスを向上させることができます。

 

このようなレンダリングの仕組みを理解しておくことでどのようなコードや構成がパフォーマンスの向上になるか考えることができるので、Webコンテンツの作成時にはぜひ少し意識してみてください。