レンダーツリーの作成、レイアウト、ペインティング

CSSOM ツリーと DOM ツリーを組み合わせたものがレンダーツリーです。このツリーを使って、各表示要素のレイアウトを計算します。また、このツリーは画面にピクセルをレンダリングするペインティング処理の入力としても使用されます。レンダリングのパフォーマンスを最適化するには、手順それぞれを最適化することが必要です。

オブジェクト モデルの構築について説明した前のセクションでは、HTML 入力と CSS 入力をベースに DOM ツリーと CSSOM ツリーを作成しましたが、この 2 つは異なる角度からドキュメントを表す独立したオブジェクトです。一方はコンテンツを記述し、もう一方はドキュメントに適用する必要があるスタイルルールを記述します。この 2 つを統合してブラウザの画面にピクセルをレンダリングするにはどうすればよいでしょうか。

TL;DR

  • DOM ツリーと CSSOM ツリーを組み合わせてレンダーツリーを作る。
  • レンダーツリーに含まれるのは、ページのレンダリングに必要なノードのみである。
  • レイアウトで各オブジェクトの正確な位置とサイズを計算する。
  • ペインティングは出来上がったレンダーツリーで行う最後の手順であり、ペインティング段階で画面にピクセルをレンダリングする。

最初の手順では、ブラウザで DOM ツリーと CSSOM ツリーを組み合わせて「レンダーツリー」を作成し、ページ上の表示可能なすべての DOM コンテンツと、各ノードのすべての CSSOM スタイル情報を取得します。

DOM と CSSOM を組み合わせてレンダーツリーを作成する

レンダーツリーを作成するには、ブラウザは基本的に次の処理を行います。

  1. DOM ツリーのルートから順に、表示可能な各ノードを確認します。
  2. スクリプトタグやメタタグなど、まったく表示されないノードもあります。こうしたノードはレンダリング出力に反映されないので省略されます。
  3. CSS で非表示に設定されているノードもレンダーツリーから省略されます。たとえば、スパンノードに「display: none」プロパティを設定する明示的なルールがあるため、上記の例のスパンノードはレンダーツリーには表示されません。
  4. 表示可能なノードごとに、適切な一致する CSSOM ルールを見つけて適用します。
  5. コンテンツと計算適用済みスタイルを持つ表示可能なノードを表示します。

最終的に、画面上の表示可能なすべてのノードのコンテンツとスタイル情報の両方を含むレンダーツリーが出力されます。あと少しです。レンダーツリーが正しく表示されたら、「レイアウト」段階に進むことができます。

ここまでで、表示可能なノードとそのノードの計算適用済みのスタイルはわかりましたが、デバイスのビューポート内でのノードの正確な位置とサイズはまだわかりません。この判断を行うのが「レイアウト」段階です(「リフロー」と呼ばれることもあります)。

各オブジェクトの正確なサイズと位置を判断するため、ブラウザはレンダーツリーのルートから順にオブジェクトを確認し、ページ上の各オブジェクトの形状を計算します。簡単な実践的な例を考えてみましょう。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

上記のページの本体にはネストされた 2 つの div があります。1 つ目(親)の div はノードの表示サイズをビューポートの幅の 50% に設定し、2 つ目(子)の div はその幅を親の 50%、つまりビューポートの幅の 25% に設定します。

レイアウト情報を計算する

レイアウト処理の出力は、ビューポート内の各要素の正確な位置とサイズを正確に取得した「ボックスモデル」です。ここでは、関連する測定値のすべてを画面上の絶対的なピクセル位置に変換するなどの処理が行われます。

表示可能なノード、そのノードの計算適用済みのスタイル、形状がわかったら、最後にこの情報を最終段階に渡します。最終段階では、レンダーツリーの各ノードを画面上の実際のピクセルに変換します。この手順を一般に「ペインティング」または「ラスタライジング」といいます。

すべておわかりになりましたか?それぞれの手順ではブラウザで大量の処理が必要になり、そのため時間がかなりかかることも少なくありません。うれしいことに、Chrome DevTools を利用すれば、上記の 3 つの段階すべてに必要な情報を入手できます。最初の「hello world」の例でレイアウト段階について説明しましょう。

DevTools でレイアウトを測定する

  • レンダーツリーの作成と、位置とサイズの計算は、[Timeline] の [Layout] イベントで行えます。
  • レイアウトが完了すると、ブラウザは [Paint Setup] イベントと [Paint] イベントを発行し、レンダーツリーは画面上の実際のピクセルに変換されます。

レンダーツリーの作成、レイアウト、ペインティングの実行に必要な時間は、ドキュメントのサイズ、適用するスタイル、そしてもちろん、実行環境のデバイスによって異なります。ドキュメントが大きいほど、ブラウザで必要な処理も増え、スタイルが複雑なほど、ペインティングに要する時間も長くなります(たとえば、単色であればペインティングに「コストはかからない」ものの、ドロップ シャドウになると計算とレンダリングにもっと多くの「コストがかかる」ことになります)。

すべて完了すると、ページがビューポートに表示されます。

レンダリングされた Hello World ページ

では、ブラウザで行われた全手順を簡単に復習しましょう。

  1. HTML マークアップを処理して DOM ツリーを作成します。
  2. CSS マークアップを処理して CSSOM ツリーを作成します。
  3. DOM と CSSOM を組み合わせてレンダーツリーを作成します。
  4. レンダーツリーでレイアウトを実行して各ノードの形状を計算します。
  5. 各ノードを画面にペインティングします。

デモページは非常にシンプルに見えるかもしれませんが、これでもかなりの処理が必要です。DOM や CSSOM が変更されたらどうなるかをよく考えてみましょう。同じ処理をもう一度繰り返して、画面に再度レンダリングする必要があるピクセルを判断しなければなりません。

クリティカル レンダリング パスを最適化することで、上記の手順 1~5 の合計所要時間を最小限に抑えることができます。 これで、できる限り短時間で画面にコンテンツをレンダリングできるようになり、また初回のレンダリング後の画面のアップデート間隔を短くする、つまりインタラクティブなコンテンツをより頻繁に更新することも可能になります。