Web技術基礎

【図解/ブラウザの仕組み】DOMとパースとレンダリング,asyncとdeferについて

ブラウザの仕組みと動作

ブラウザのコンポーネント

ブラウザのコンポーネントを以下に示します。

引用元:ブラウザの仕組み: 最新ウェブブラウザの内部構造

この記事での説明のメインは [Rendering Engine], [Networking], [JavaScript Interpreter] の 3 つです。

Rendering Engine とは

レンダリングエンジンは、ブラウザの中で画面表示に特化したモジュールです。

レンダリングエンジンは Networking により Web サーバから html ファイルを取得し、それをブラウザ上に表示させるまでを担いますが、それまでには大きく 4 つのプロセスを行います。

プロセスとは「①パース」、「②レンダーツリー構築」、「③レイアウト」、「④ペインティング」です。

ブラウザが Web サーバから情報を取得し、画面表示するまでの流れを以下に示します。

①パース: html ファイルをパースすることにより DOM ツリーCSSOM ツリーを構築します。CSS の読み込みや JavaScript の実行もパースに含まれます。

②レンダーツリー構築:DOM ツリーと CSSOM ツリーからレンダーツリーを構築します。

③レイアウト:ブラウザの画面幅やスクロール位置を意識し、画面上にどの部分をどのように表示するかを計算します。

④ペインティング:実際に画面に描画して表示します。

Edge や Chrome は Chromium というオープンソースをベースにしていますが、Chromium では『BLINK』というレンダリングエンジンを採用しています。

パースとは

ブラウザは Web サーバと TCP 3way ハンドシェイクで TCP コネクションを確立した後、GET メソッドを使って html ファイルをダウンロードします。(POST メソッドでブラウザから Web サーバへデータを送るときもありますが、その際も html ファイルが返ってきます。)

ブラウザは、ネットワークから受信した html ファイル (もしくはディスクに配置された html ファイル) を『パース (解析)』することにより、ブラウザに割り当てられたメモリ上に『DOM』という形式にして配置します。

また、スタイルシート css ファイルについても同様に、パースすることにより『CSSOM』という形式にしてメモリに配置します。

つまり、『パース』とは、ネットワークから受信した (もしくはディスクに配置された) html を DOM に、css を CSSOM としてメモリへ展開することを言います。

その他、JavaScript の実行や画像ファイルの読み込みも含まれます。

DOMとは, CSSOMとは

DOM は Document Object Model の略で、html や xml ファイルを扱うアプリケーション (今回で言えばブラウザ) が、html/xml ファイルのドキュメントをオブジェクト化するためのモデルです。

もう少し平たく言うと、html/xml ファイルを PC のメモリ上にどのような形式で格納するのかを定義したものです。

このような定義を行うことで、例えば javascript で操作したいときにどのタグを狙って操作するかを一意に定めることができます。

html や xml のタグは DOM のオブジェクトとして「要素 (エレメント)」や「ノード」と表現されます。html/xml のタグと DOM の要素は 1 対 1 に対応しています。

html/xml タグの入れ子構造は DOM 要素のツリー構造 (DOMツリー) として表現されます。

DOM の仕様は W3C により定められています。

DOM Standard

css は html のような入れ子構造ではありませんが、CSSOM という似たような形式でどのようにパースされるべきか定義されています。

CSS Object Model (CSSOM)

DOM の要素が多いと CPU/メモリの消費が大きくなるため、ある程度に抑えたほうが望ましいです。

PageSpeed Insight では以下のような基準を設けています。

  • 1 ページあたりの DOM 要素数が 1500 以下であること
  • DOM の深さ (入れ子の深さ) が 32 以下であること
  • 1 つの DOM 要素の下に、子要素が 60 以下であること

この基準を超えると「過大な DOM サイズの回避」「DOM サイズが大きいと、メモリの使用量が増え、スタイルの計算に時間がかかり、レイアウトのリフローというコストが発生します。」という警告が示されます。

レンダリングとは

レンダリングとは、パースしてメモリへ展開した DOM ツリーと CSSOM ツリーを使い、ブラウザの画面に表示させるまでの処理のことを言います。

定義が曖昧ですが、ここでは「②レンダーツリー構築」「③レイアウト」「④ペインティング」のプロセスをレンダリングとしています。

レンダリングブロックとは

レンダリングはパース後に行われますが、html ファイル全てがパースされるのを待っているわけではありません。

ある程度の単位 (8 KB 等) でパースしたものを元に DOM/CSSOM ツリーを作成し、逐次レンダリングを行っていきます。

ですが、デフォルトでは以下のようなケースでレンダリングの開始が遅れる原因になります。

  1. CSS ファイルの読み込みが遅れている場合
  2. JavaScript ファイルをダウンロードする場合

PageSpeed Insights にて「レンダリングを妨げるリソースの除外」「ページの First Paint をリソースがブロックしています。重要な JavaScript や CSS はインラインで配信し、それ以外の JavaScript やスタイルはすべて遅らせることをご検討ください。」と表示されたらこの問題に直面しています。

1 の CSS についてはタイムアウトまで CSSOM ツリーが出来るのを待ってから (もしくはタイムアウトを待ってから) レンダリングを開始するため、html ファイルのなるべく先頭に近い場所に css のリンクを配置するのが基本戦略です。

また、2 の JavaScript については、ダウンロードし実行するまでの間、パースが止まるため、結果としてレンダリングの開始が遅れてしまいます。(実行中もパースは止まりますが、これは避けようがありません。ここでは JavaScript ファイルのダウンロード中にパースが止まることを問題視しています。)

このような事象を「レンダリングブロック」と呼びます。

細かい話をすると、JavaScript の場合はレンダリングをブロックしているわけではなく、ダウンロード時間の分だけパースがブロックされ、その結果、レンダリング開始が遅れるのです。

実際、Google Developerの以下リンクでは、「ブラウザは、DOM と CSSOM の両方が揃うまでレンダリングをブロックします」と明記していますが、

レンダリング ブロック CSS

以下リンクでは、『JavaScript は DOM の構築をブロックして、ページのレンダリングを遅らせてしまうことがあります。』という表現になっています。

JavaScript を使用してインタラクティブにする

CSS のレンダリングブロック解消

CSS のレンダリングブロックを解消するには、「メディアタイプ」と「メディアクエリ」を使う方法があります。

例えば以下の記述をした場合、style.css はレンダリングブロックの対象になりますが、print.css と other.css はダウンロードは行いますが、CSSOM に組み込まれなくてもレンダリングは開始されるようになります。(media=print は印刷時に適用されるスタイル、media="(min-width: 40em)" は画面幅が 40em 以上の場合に適用されるスタイル)

<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">

JavaScriptのレンダリング (パース) ブロック解消

また、JavaScript のレンダリングブロック (パースブロック) を緩和するには 2 つの方法があります。

1 つは JavaScript のインライン化です。これは、外部参照 (別ファイル) にしているスクリプトを html ファイル内に含めてしまうことです。

インライン化してしまえばダウンロードが不要になるため、ダウンロードによるブロック時間は無くなります。(前述の通り、どちらにせよ JavaScript の実行中はパースはブロックされますが)

もう 1 つは JavaScript の非同期化です。非同期化の方法には async と defer の 2 つのやり方があります。

asyncとdeferのメリット/デメリット・使い分け

JavaScript を非同期 (async/defer) にした場合、外部参照のJavaScript ダウンロードと並行してパースを続けることができます。

それぞれどのように使い分けるべきか、それぞれの特徴を見ていきましょう。

async の場合、JavaScript ダウンロード直後にスクリプトを実行しますが、defer の場合はパースが一番下まで終わった後にスクリプトを実行します。

引用元:Loading Third-Party JavaScript

async の場合は「書かれている順番ではなく」ダウンロードが完了した順番に実行されます。そのため、レンダリングを早く開始できる可能性が高いですが、実行中近くに複数の JavaScript リンクがある場合、順番が入れ替わる可能性があります。なので jQuery 等のライブラリに依存するスクリプトがある場合などは順番が入れ替わるとエラーになる可能性があります。

一方、defer の場合はそのようなことはなく、パース完了後に書かれている順番に実行されていきますのでその点は安心です。ただし、レンダリングの開始が async より遅れる可能性が高いです。

コメント

タイトルとURLをコピーしました