LoginSignup
10
5

More than 3 years have passed since last update.

【曖昧さ回避】ブラウザレンダリングにおける「ファイルの読み込み」が意味するものとは

Last updated at Posted at 2020-04-02

「ファイルの読み込み」とは

ブラウザレンダリングの仕組みを解説するサイトや書籍には、「ファイルを読み込んで〜」のような説明が多くあります。
自分がレンダリング工程を勉強しているときに、この「読み込み」という言葉がファイルのDownload(転送)を指すのか、ファイルのParse(解析)を指すのか、はたまたレンダリング全体のことを言っているのか、説明する場面によって意味が変わる曖昧な言葉だなーと感じていました。

ここではブラウザレンダリングの仕組みについて、1.HTMLのみ、2.HTMLとCSS、3.HTMLとJavaScript、4.HTMLとCSSとJavaScriptの4パターンに分けて、レンダリングフローに定義された言葉に当てはめながら説明していきたいと思います。

(検証環境:Google Chrome バージョン: 80.0.3987.87)

ブラウザレンダリングの仕組みの大枠

ブラウザレンダリングのフローは大きく4つの工程に分けられ、それぞれの工程は更にいくつかの細かい工程に分けられます。
(参考:Webフロントエンド ハイパフォーマンス チューニング -久保田 光則 (著)
ブラウザレンダリングの流れ.png

  • Loading(データのダウンロード・解析)
    • Download
    • Parse
  • Scripting(JSの実行)
  • Rendering(スタイルの計算、当て込み)
    • Calculate Style
    • Layout
  • Painting(描画)
    • Paint
    • Rasterize
    • Composite Layers

図を見ていると全ての工程がシリアル(直列)に進んでいくように誤解しやすいのですが、実際はそうではありません。
レンダリングエンジンがページ表示を最適化する中で、部分的にでも準備ができた段階で、都度次の工程に進むこともあります。

本記事では主にLoading(Download、Parse)Scriptingの工程に関して、ファイルごとにどのように影響を及ぼし合い、レンダリングの処理順が決まっているかについて説明します。

RenderingPaintingの工程を含むブラウザレンダリング全体の仕組みについては以下記事が詳しいです。
フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説! | LeapIn

1.HTMLのみ

はじめに外部ファイル「読み込み」記述が一切ない純粋なHTMLファイルについて、
ブラウザ検索バーにURLを入力し、HTTPプロトコルで通信してページを表示する場合を考えます。
(参考:ネットワークやTCP/IPやHTTPの基本(初学者向け) - Qiita

レンダリングの工程としては、まずHTMLDownloadが始まりますが、
ここでのポイントは、サーバからHTMLファイルなどのリソースが転送される手法は0か1の転送ではなく、
セグメントに分割しながら転送されるということです。
(どのくらいまとめて送るのかについてはサーバサイドで制御するようです)

前提として、ブラウザはUX向上のため画面に何も表示されていない時間を短くするように動きます。
よって全てのHTMLDownloadが完了していなくても、転送されたHTMLセグメントを元にParse(DOMツリー構築)や後続の処理が進み、準備ができたDOMから画面描画が始まります。

スクリーンショット 2020-03-29 2.12.07.png
上記はChrome DevToolsのNetworkパネルであり、一つのHTMLファイルをダウンロード完了するまでの解析図です。(テスト用にサーバサイド(PHP)でファイルの転送や解析速度を調整しています)

Waiting(TTFB:Time To First Byte)とはファイル転送リクエストを送ってからクライアント側で最初のデータを受け取るまでにかかる時間(主にサーバサイドの処理時間)であり、Content Downloadとは最初のデータを受け取ってから全てのデータを受け取りきるまでにかかる時間です。

解析グラフによるとContent Downloadに合計2sかかっていますが、その間も転送されてきているデータを元に別の処理(Parse、Rendering、Painting)が都度進んで描画が始まっており、それは同Performanceパネルで解析することができます。↓
スクリーンショット 2020-03-29 19.13.47のコピー.png
データを受け取る(Receive Data)たびに、HTMLParse(DOM構築)のフェーズを経て、Composite Layersまでの描画工程を完了していることが分かります。

このように準備ができたところから都度描画が行われることで、First Paint(画面に最初になにかしらが描画されタイミング)や、First Meaningful Paint(画面に最初にユーザーに意味のある表示がされたタイミング)などの表示タイミング差が存在します。

参考:Ace the Lighthouse Audit: Best Practices for Consistent Interactivity | Lumavate

2.HTMLとCSS

head要素の中のlink要素に外部CSS「読み込み」記述がある場合を考えます。

HTML
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>

CSSのDownload

この場合も、まずHTMLDownload、Parseが始まり、解析途中でlink要素を見つけた段階でCSSDownloadが始まります。↓
スクリーンショット 2020-03-29 14.07.42.png
CSSのDownloadはHTMLのParseをブロックしないので、CSSDownload中もHTMLParseが並行して進みます。
そしてその先に再び外部CSS「読み込み」記述ががあれば、同時に複数のCSSDownloadが始まります。

ただし、モダンブラウザでは(同じドメインの)TCP接続は同時に6本までという制限があるため、7本目以降の接続は前の接続の終了を待ってからとなります。
スクリーンショット 2020-03-29 14.45.11のコピー.png
見ての通りこれではダウンロードしたいファイルが多いほどページ表示速度が遅くなってしまいます。

そのため、対応策としてファイルを可能な限りまとめてリクエスト必要数を抑えたり、CDNなどを利用してあえて別ドメインから接続することでスループットを上げたり、一つのTCP接続で同時に複数のリクエスト/レスポンスを処理できるhttp/2プロトコルで通信するなどの手法が存在します。

参考:そろそろ知っておきたいHTTP/2の話 - Qiita

CSSのParse

CSSもHTMLと同様にDownloadの次の工程として、Parse(CSSOMの構築)の工程があります。
考慮すべき注意点は以下です。

  • CSSParseは見かけ上はHTMLParseと並行して行われる。
  • HTMLは描画工程に進もうとするDOMの、直前までに記載されているCSSのLoading(Download、Parse)が完了しない限り、Renderingフローに進まない。(描画処理が行われない)

HTMLParseCSSParseはどちらもレンダリングエンジンのmainスレッドで行われますが、mainスレッドでは同時に一つの処理しか行えないため、それぞれの処理が同時に走ることはありません。
ですが、HTMLParseのアイドル時間などにCSSParseが進むため、見かけ上は2つが並行して行われているように見えます。
(そもそもCSSParseにかかる時間はブラウザレンダリング全体の時間からすると極めて短く、議論に上がりにくい部分のようです。)

また、CSSのLoadingが進行中の場合は、たとえHTMLParseが先に完了していてもRenderingなどの次の工程に進まず、結果として画面描画が行われません。

これはブラウザがFOUCFlash of Unstyled Contentの略。スタイルがついていないコンテンツが一瞬表示されること)を防ぐために、CSSParseの完了を待ってスタイルが適応された画面描画を行おうとするためです。
スクリーンショット 2020-03-29 15.35.54のコピー.png
上記Performanceパネル解析図を見ても、Finish LoadingCSSParseの完了)まで、Calculate StyleなどのRendering工程に進んでいない(画面描画が行われていない)ことが分かります。

3.HTMLとJavaScript

以下のようにhead要素の中にscript要素を記述して、外部JavaScriptファイルを「読み込む」場合を考えます。

HTML
<!DOCTYPE html>
<html>
  <head>
    <script src="main.js"></script>
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>

JSのDownloadとScripting

HTMLParseが始まってscript要素に到達するとJSのDownloadが始まります。
その時に重要なポイントが、JSのDownloadとScripting(実行)はHTMLParseをブロックするということです。

一度JSのDownloadが始まると、ダウンロードしたJSのScripting工程が完了しない限り、それ以降のHTMLParseが行われません。
これが、JSの記述はbodyの最後に記述するべきと言われる理由の一つです。
スクリーンショット 2020-03-29 20.29.02のコピー.png
上記図より、Send RequestJSDownloadが始まると、Evaluate Script工程が完了するまでHTMLParseが行われていないことが分かります。

async属性とdefer属性

script要素によるJSの「読み込み」記述はそれ以降のHTMLParseをブロックしますが、script要素asyncdeferの属性をつけることによってJSのDownloadを非同期に行い、HTMLParseと同時に処理することができます。

HTML
<script src="main.js" async ></script>

<!-- もしくは -->

<script src="main.js" defer ></script>

以下は先程と同じ記述で、defer属性を使用したときのPerformanceパネルの解析結果です。
スクリーンショット 2020-03-29 20.34.04のコピー.png
JSのDownloadが開始(send Request)しても、HTMLParseがブロックされずに先の工程に進み、最終的にComposite Layersまで完了して画面描画が行われているのが分かります。
その後JSのDownloadが完了した段階で、Scripting(Evaluate Script)処理が行われています。

参考:scriptタグに async / defer を付けた場合のタイミング - Qiita

4.HTMLとCSSとJavaScript

CSSとJavaScriptの両方の「読み込み」記述を書く場合です。
以下のようにlink要素の直下にscript要素を入れてみます。

HTML
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <script src="main.js"></script>
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>

CSSDownloadHTMLParseをブロックしないため、HTMLParsescript要素の記述に到達しJSのDownloadが始まります。
先程「HTMLは直前までのCSSLoading(Download、Parse)が完了していない限り、Renderingフローに進まない」と説明しましたが、実は同様にJSも直前までのCSSLoading(Download、Parse)が完了していない限り、Scriptingの工程に進まない性質があります。

つまりこの場合、CSSよりもJSのほうが速くDownloadが完了したとしても、CSSParseが完了するまでScriptingが待機状態になるということです。
スクリーンショット 2020-03-29 16.35.27のコピー.png
↑JSのほうがCSSよりも1s速くDownloadが完了していますが、
スクリーンショット 2020-03-29 16.34.37のコピー2.png
↑CSSのLoading(Download、Parse)完了を待ってから、Scripting(Evaluate Script)処理が実行されていることが分かります。

参考:DOMContentLoaded周りの処理を詳しく調べてみました - Qiita

ブラウザのプリロード機能

以下のようにJSの「読み込み」記述をCSSよりも前に書いた場合を考えます。

HTML
<!DOCTYPE html>
<html>
  <head>
    <script src="main.js"></script>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>

defer属性async属性がついていないscript要素による外部JSファイルの「読み込み」なので、JSのDownload、Scriptingが完了するまでそれ以下のHTMLParseが進まない、つまりCSSDownloadも進まないはずです。

しかし、モダンブラウザではその限りではありません。
NetWorkパネルを見てみると、JSとCSSのDownloadが同時に行われていることが分かります。
スクリーンショット 2020-03-29 21.22.31のコピー.png
実はChromeなどのモダンブラウザには、HTMLParseが進んでいない部分についてもDownloadが必要な記述がないか確認し、もしあれば事前にそのファイルのDownloadを開始する機能があります。(Preload Scanner

よってこの場合も、ブラウザはJSのDownload中にその先にあるCSSの「読み込み」記述を読み取り、CSSDownloadも同時に進めることでレンダリングを高速化しているのです。

※Preload Scanner機能で事前処理できるのはDownloadの工程だけです。ParseScriptingの工程は本来のレンダリングフローに沿って行われます。

参考:rel="preload"を極めるために必要な2種類のプリロード機能 | Raccoon Tech Blog

まとめ

  • HTMLはセグメントごとにDownloadが行われ、都度Parseなどの先の工程に進む
  • CSSのDownloadHTMLのParseをブロックしない
  • CSSのParseは見かけ上はHTMLParseと並行して行われる
  • HTMLは直前までのCSSLoading(Download、Parse)が完了していない限り、Renderingの工程に進まない
  • JSのDownloadScripting(実行)HTMLのParseをブロックする
  • JSも直前までのCSSLoading(Download、Parse)が完了しない限り、Scriptingの工程に進まない

誤った解釈等ございましたら、ご教授お願いいたします。。

参考

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5