PerfData

二つのパーサ

HTMLとCSSのマッチング処理という名の舞踏会

2023年5月8日
著者: 竹洞 陽一郎

はじめに

会議は踊る、されど進まず

タレーラン

前回の記事では、Preload Scannerについて解説しました。
Preload Scannerは、Shallow Parsingによって、読み込むべきファイルを見つけ出して、バックグラウンド処理でファイルを読み込みます。
その中にはCSSも含まれます。

しかし、CSSでbackground-imageを使ったり、importを使ったりすると、Preload Scannerは仇となって、遅延要因になります。
また、CSSの複雑度によって、マッチング処理の計算量が増大することになります。
今回は、HTML文書の装飾表示で重要な役割を果たすCSSでの注意点を解説します。

Preload Scanner

CSS Background Imageの利用を避ける

CSS Background Imageとは、CSS(Cascading Style Sheets)の機能の一つで、HTML要素の背景に画像を設定する際に使用されるスタイルプロパティです。
このプロパティを用いることで、HTML要素に対して画像を背景として表示し、デザインを豊かにすることができます。
CSS Background Imageは、以下のような形式で記述されます。


selector {
  background-image: url("path/to/image.jpg");
}

ここで、selectorは対象となるHTML要素を指定し、url("path/to/image.jpg")には表示したい画像ファイルへのパスが入ります。

CSS Background Imageの問題点

CSS Background Imageは、2008年に発売されたスティーブ・サウダーズ氏が書いた「ハイパフォーマンスWebサイト ―高速サイトを実現する14のルール」(オライリージャパン)で一躍注目を集めました。
CSSスプライトを利用することで、画像を統合して配信して、画像を一気に送るという手法です。

しかし、皆さん、今年は2023年です。
15年もの間、技術は停滞していると思いますか?
画像については、decoding="async"loading="lazy"属性によって、バックグラウンド処理や遅延読み込みができるようになりました。

現在においては、CSS Background ImageはWebパフォーマンスに影響を与えるボトルネックとなっています。
CSS Backgroud Imageは、imgタグで読み込む画像と異なり、decoding="async"やloading="lazy"といった画像読込をメインスレッドに影響を与えなくするためのオプションが存在しません。
decoding="async"を提唱したAppleのSimon Fraser氏は、2016年10月18日にGitHub上で以下のようにCSSの画像についても提案しました。

大きな画像のデコードは、メインスレッドを数百ミリ秒以上ブロックし、流れるようなアニメーションやユーザーとのインタラクションを中断することがあります。
現在、Web制作者が非同期で画像をデコードすることを指定する方法はありません。
そのため、UIの停止を避けることができないシナリオが存在します。

この問題を解決するために、画像要素に「async」属性を提案します。
この属性は、制作者が非同期デコードをリクエストしたことをUA(ユーザーエージェント)にヒントとして与えます。
これにより、「load」イベントが発火した後、画像がデコードされる前にUAが画像を描画する場合、UAは画像の描画をブロックせず、描画しないことが許可されます。

デコードされた画像フレームが利用可能になったときに制作者に通知するため、画像要素で新しいイベント「ready」を発火することを提案します。
これにより、UIの停止に敏感なコンテンツで完全にデコードされた画像が必要な制作者は、「ready」イベントを待ってから画像を表示する何かを行うことができます。
(例えば、CSSトランジション)

アニメーションGIFのように、一部の画像は繰り返しフレームをデコードします。
また、UAは静止画像のデコードされたフレームを破棄し、再デコードが必要になることがあります。
この場合、「ready」イベントは最初に表示可能なフレームが利用可能になったときに一度だけ発火することを提案します。

問題点:
「async」は現在、非同期の読み込みを意味するために使用されており、画像に対しても同じ意味で使いたい場合があります。
新しい属性の名前を「asyncDecode」などに変更することを検討してください。

これは、画像要素の問題のみを解決し、CSS画像には対応していません。
CSS画像に対して非同期デコードを許可するCSSプロパティを追加し、適用される要素にイベントを発火することができます。

これを実現するためには、CSSに対するShallow Parsingの実装が必要になります。
未だ、CSSのShallow Parsingは仕様化されていません。

流石にCSSスプライトを使っているサイトは見かけなくなりましたが、Background Imageを使っているサイトは頻繁に見かけます。
CSS Background Imageを使うほどに、画像のダウンロード時間がCSSの処理時間に足され、各種計測ではダウンロード時間が伸びて表示される点に注意しましょう。
そして、現状では、CSS Background Imageをバックグラウンド処理したり、遅延読込する方法は仕様化されていないのです。

現状においては、CSS Background Imageを使うのは避けて、画像の読込はできるだけ、<img>を使いましょう。

CSSの@importルールの使用を避ける

CSSの@importルールは、Preload Scanner(事前読み込みスキャナ)の恩恵を受けられません。
これは、Preload ScannerがHTMLの要素を解析し、リソースの読み込みを事前に開始することを目的としているためです。

Preload Scannerは、HTMLファイル内にある<link>要素や<script>要素などをスキャンし、リソースの読み込みを早めることができます。
しかし、@importルールはCSSファイル内に記述されるため、Preload Scannerが直接的にそのルールをスキャンできません。
その結果、リソースの読み込みが遅延します。

また、@importルールは、CSSファイルがシリアル(連続)に読み込まれることを意味します。
これは、CSSファイルが読み込まれるまで次のCSSファイルの読み込みが開始されないため、ページのレンダリング速度に悪影響を与えます。

この問題を解決するためには、@importルールの代わりに、HTMLファイル内に複数の<link rel="stylesheet">要素を配置し、リソースをパラレル(並列)に読み込むようにすることが推奨されます。
これにより、Preload Scannerの恩恵を受け、ページのレンダリング速度を向上させることができます。

CSSの計算量

CSSの計算量は、ブラウザがスタイルルールを解析して適用するために必要な時間やリソースを指します。
計算量は、主に以下の要素によって影響を受けます。

これらの要素が増えるほど、CSSの計算量は増加し、結果的にページのレンダリング速度が遅くなります。
したがって、パフォーマンスを向上させるためには、CSSの計算量を最小限に抑えることが重要です。

Right To Leftマッチング

Right To Leftマッチングは、ブラウザがCSSセレクタを解析する際に使用するアルゴリズムの1つです。
このアルゴリズムは、セレクタの右端から左端に向かってマッチングを行うため、Right To Left(RTL)と呼ばれます。
RTLマッチングは、ブラウザがCSSの計算量を効率的に処理するために重要な役割を果たします。

RTLマッチングのプロセス

RTLマッチングは、以下の手順で実行されます。

  1. ブラウザは、セレクタの右端(キーストーン)からマッチングを開始します。
  2. キーストーンが要素にマッチする場合、左隣のセレクタに進みます。
  3. すべてのセレクタがマッチした場合、スタイルルールが適用されます。

例えば、以下のようなセレクタがあるとします。


.parent .child {
  color: red;
}

このセレクタでは、.childがキーストーンになります。
ブラウザは、.child要素からマッチングを開始し、その親要素が.parentクラスを持っているかどうかを確認します。
もし親要素が.parentクラスを持っていれば、スタイルルールが適用されます。

RTLマッチングのメリット

RTLマッチングの主な利点は、効率性の向上です。
このアルゴリズムは、最初に最も具体的なセレクタからマッチングを始めるため、不要なマッチングの試行を大幅に削減できます。
また、右端のキーストーンがマッチしない場合、それ以上のマッチングを試行しないため、計算量が節約されます。

さらに、RTLマッチングは以下の点でも効果があります。

セレクタのマッチングが早い段階で失敗することが多いため、全体のマッチング速度が向上します。
不要なセレクタのマッチング試行を減らすことで、レンダリング速度が改善され、パフォーマンスが向上します。

RTLマッチングの計算量

計算量は、アルゴリズムの効率を測定するために使用される指標で、入力サイズに対して必要なステップ数やリソースを示します。
CSSのRight to Leftマッチングにおける計算量は、次の要素に依存します。

CSSのRight to Leftマッチングの計算量は、一般的に、O(n * m)です。
これは、各要素に対してセレクターのすべての構成要素を照合する必要があることを意味します。
ただし、実際にはブラウザが最適化を行い、照合プロセスを高速化するため、計算量は必ずしもO(n * m)になるわけではありません。

要素数が増加したり、セレクターが複雑になると、Right to Leftマッチングの計算量が増大し、ページのレンダリング速度が低下することがあります。
そのため、シンプルなセレクターを使用し、不要なネストを避けることが、パフォーマンスの向上に役立ちます。

RTLマッチングを考慮したCSS設計

RTLマッチングを効率的に活用するためには、以下のようなCSS設計が推奨されます。

セレクタの簡略化
複雑なセレクタは、計算量を増やすだけでなく、メンテナンスも困難になります。
セレクタをシンプルに保つことで、RTLマッチングの効率を最大限に引き出すことができます。
最も具体的なセレクタを右端に配置
RTLマッチングは、最も具体的なセレクタからマッチングを開始するため、この原則に従ってセレクタを配置することで、マッチング速度が向上します。
深いネストを避ける
深いネストは、CSSの計算量を増加させるため、避けることが望ましいです。
フラットな構造にすることで、RTLマッチングの効率を向上させることができます。

まとめ

CSSの計算量は、Webページのパフォーマンスに大きな影響を与えます。
Right To Leftマッチングは、CSSセレクタのマッチングを効率的に行うためのアルゴリズムで、セレクタの簡略化や最も具体的なセレクタを右端に配置することで、その効果を最大限に引き出すことができます。
最適なパフォーマンスを得るために、これらの原則に従ったCSS設計が重要です。