ブラウザにはやさしくしたいよね?ブラウザの仕組みを意識しよう!
WEB制作のベストプラクティスとして 「CSSのセレクタは浅くする」 「アイコン画像はCSSスプライト画像にする」 「JSの読み込みはbodyの閉じタグ付近にする」 ということは当たり前のように行われます。
何故それがベストなのか、という理由は言葉では理解していると思います。 今回は、その理由をブラウザの目線で掘り下げてみます。
なぜCSSのセレクタは浅い方がよいとされるのか?
CSSのセレクタは、浅い方がよいとされていますよね。
/* good */ .a { color: #333; } /* bad */ .a .b .c .d .e .f .g { color: #333; }
作り手側からすればCSS設計の話になりますが、ブラウザ目線では違います。 ブラウザからすれば、セレクタは浅い方が文書の解析が楽になるのです。
そもそもブラウザはなにをしているのか?
ブラウザがやっていること
ブラウザが行っている処理の流れは主に以下の3つです。
・リソース(HTML, CSS, JS, etc)の取得 ・リソースの解析 ・解析結果の描画
リソースというのは、文書ファイルや画像ファイルのことです。 リソースをダウンロードし、それをブラウザ内で解釈できる形に解析します。 そして、解析した結果から、画面に描画する処理を行います。
リソースの解析
リソースの解析は下記のフローで行われます。
・リソースを解析してブラウザ内部の表現に変換する → HTMLはDOMツリーに変換 → CSSはCSSOMツリーに変換
ブラウザ内部の表現というは「ツリー」、つまり木構造のことです。 ブラウザは、HTMLとCSSを木構造として認識しているのです。
木構造のイメージは下記画像をご覧ください。
ブラウザがHTMLとCSSをどう認識しているかイメージがわいたでしょうか。
解析結果の描画
ブラウザは、木構造で解釈したDOMツリーとCSSOMツリーを使い、今度はレイアウトツリーというものを作成します。 レイアウトツリーは、DOMツリーとCSSOMツリーとをマッチングさせた結果で、どのタグにどのCSSのスタイルを適応するかをマッチングさせたものです。 レイアウトツリーまで作成して、やっと画面に描画することが可能になります。
「CSSセレクタが浅い方が~」はマッチング処理の話
DOMツリーとCSSOMツリーのマッチングは総当りです。 DOM要素とCSSルールセットを手当たり次第に総当りでマッチングします。
例えば、以下の画像のようなHTMLとCSSがあるとします。
これ、総当りならCSSのネストが深い方が効率よくありませんか?
.a → .b → .c → .d → .e
という風に、明示的に親子関係を表現した方が、ブラウザは無駄な走査を行わなくて済みそうですよね?
マッチング処理はセレクタの右から左に解釈する
ところがどっこい、なんとマッチングは左側から走査しません。 セレクタの右側から解釈していきます。
ブラウザはまず、HTMLのタグを総当たりで調べて、タグが指定のセレクタに適合するかを調べます。 そして、一つ左のセレクタがあれば、今度は親要素のタグをすべて総当りで調べないといけません。 CSSのセレクタが深ければ深いほど、何度も親要素を走査しなくてはなりません。
なぜJavaScriptはbodyの閉じタグ付近に書くのか
JSファイルの読み込みはbodyの閉じタグ直前に書くとよいとされています。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>タイトルだよ</title> </head> <body> <main>ステキなコンテンツ</main> <!-- bodyの閉じタグの前にJavaScriptを書くのが良いとされる --> <script src="script.js"></script> </body> </html>
その理由は、レンダリングをブロックしてしまうから、というのはよく聞く話ですよね。 では、なぜJavaScriptでレンダリングが中断されてしまうのでしょうか。
レンダリングエンジンは解析時にJSを実行する
レンダリングエンジンは、HTML解析時にJSを実行しつDOMツリーを構築します。 その理由は、DOMツリーはJS実行結果に依存することがあるためです。
JSの実行結果に依存するHTML文書構造というのは、例えば、下記のような場合です。
<body> <p><script>document.write('文字を表示するよ!')</script></p> </body>
HTML文書をJSが書き換えていますよね。こういう場合があるので、レンダリングエンジンはJavaScriptが登場したら作業を中断して一旦JavaScriptの実行を待つのです。
レンダリングエンジンはJSを実行しないと不安
レンダリングエンジンは、scriptタグの中身は実行しないとわかりません。 わからないから、実行しておかないと「HTMLが書き換えられるかもしれない」と不安になってしまいます。
レンダリングエンジンがなぜJavaScriptに対してこんなに不安になってしまうかというと、JavaScriptはレンダリングエンジンの担当範囲ではないからです。
JSの実行とレンダリングは別のコンポーネント
レンダリングエンジンのほかに、JavaScriptエンジンというものがあります。 レンダリングエンジンは、HTMLの解析と描画を担当しています。 JavaScriptエンジンは、JavaScriptの解析と実行を担当しています。
つまり、scriptタグの中身のことはJavaScriptエンジンの領分なのです。 そのため、レンダリングエンジンはscriptタグが出てくるたびにJavaScriptエンジンにバトンタッチして、JavaScriptの実行結果がでるまで待機することになってしまいます。
基本的に、画面の初期表示をJavaScriptで書き換えることはありませんよね? HTMLを書き換えないのに、レンダリングエンジンの邪魔をしては申し訳ないので、bodyの閉じタグ付近にscriptタグを集約させるのです。
なぜ画像をCSSスプライトにするのか
CSSスプライトは、複数の画像を1つにまとめてCSSのbackgroundで表示するものですが、なぜわざわざこんなことをする必要があるのでしょう。 作り手としては、ボタンが画像の場合、マウスオーバー時に画像を読み込んでボタンがチラつくのを防ぎたい、ということがあったりしますね。 しかし、ブラウザ目線ではそうではありません。
前述しましたが、ブラウザがやっていることの流れは下記のものです。
・リソース(HTML, CSS, JS, etc)の取得 ・リソースの解析 ・解析結果の描画
CSSスプライトは「リソースの取得」に関することです。
・リソース(HTML, CSS, JS, etc)の取得 ← コレ! ・リソースの解析 ・解析結果の描画
リソースの取得、というのはつまりなにを行っているかというと、下記の流れのようになっています。
・サーバーにHTTPリクエストを送信する ・サーバーからHTTPレスポンスが返ってくる ・リソースの取得完了
ブラウザは、サーバーに対して「ファイルを頂戴!」とリクエストしているわけですね。 実は、リクエストは最大で6つ程度しか同時に行えないのです。
並列に6つしか処理できないという制限があるために、CSSスプライトに意義があることがわかります。
ボタン画像やアイコン画像というのは、ファイルサイズは小さいですが、数が肥大化しがちです。 1画面に100個の画像ファイルがあったら、それを6つずつしか捌けないとなると、他のファイルは大渋滞です。
アイコン画像が100個あれば100リクエスト。 CSSスプライトにすれば1リクエストで済みます。 ファイルサイズは同じでもリクエスト数が減ればブラウザとしては負荷が減って楽ができます。
実際にはアイコン画像で100個もいかないかもしれません。 しかし、リソースは画像以外にもCSSもあればJSもあります。フォントファイルもあるかもしれませんし、動画だってあるかもしれません。
有名どころのサイトでも、大体100リクエストは超えています。 トータル100リクエストのうち、アイコン画像が20個あったとしたら、CSSスプライトにするだけで総リクエスト数の減る割合がどれだけ大きいかは、想像に難くないですね。
最後に
ブラウザにやさしいサイトということは、ブラウザが高速に表示できるサイトということでもあります。 ページスピードはコンバージョンに大きく影響を与えます。 コンバージョン率を上げるためにも、サイトをチューニングして、ブラウザにやさしいサイトを目指しましょう。