JavaScript は一般に、視覚変化のトリガーとなるものです。視覚変化はスタイル操作を通じて直接行われることもあれば、データの検索やソートのように、計算が最終的に視覚変化につながることもあります。タイミングの悪い JavaScript や長時間実行される JavaScript はパフォーマンス低下の原因になることが多いため、可能な限り JavaScript の影響を最小限に抑える必要があります。
スタイルの計算
要素の追加や削除、属性やクラスの変更、アニメーションの再生によって DOM を変更すると、ブラウザで要素スタイルの再計算が発生するほか、多くの場合、ページ全体またはページの一部のレイアウトが再計算されます。このプロセスは「スタイル計算」と呼ばれます。
ブラウザは、適合セレクターのセットを作成して、特定の要素に適用されるクラス、疑似セレクター、ID を決定することで、スタイルの計算を開始します。次に、一致するセレクタからスタイルルールを処理し、要素の最終的なスタイルを特定します。
インタラクションのレイテンシにおけるスタイル再計算の役割
Interaction to Next Paint(INP)は、ユーザー入力に対するページの全体的な応答性を評価するユーザー中心のランタイム パフォーマンス指標です。ユーザーがページを操作してから、ブラウザが次のフレームを描画し、ユーザー インターフェースの対応する視覚的更新を表示するまでのインタラクションのレイテンシを測定します。
インタラクションの重要な要素は、次のフレームのペイントにかかる時間です。次のフレームを表示するために行われるレンダリング作業は、レイアウト、ペイント、合成作業の直前に発生するページスタイルの計算など、多くの部分で構成されます。このガイドではスタイル計算コストに焦点を当てていますが、インタラクションの合計レンダリング時間の一部を短縮すると、合計レイテンシも短縮されます。
セレクターの複雑性の軽減
CSS セレクタを簡素化すると、ページのスタイル計算を高速化できます。最もシンプルなセレクタは、クラス名のみで CSS の要素を参照します。
.title {
/* styles */
}
しかしながら、プロジェクトの拡張に伴って、CSS はより複雑になり、最終的には次のようなセレクターを使用することになります。
.box:nth-last-child(-n+1) .title {
/* styles */
}
これらのスタイルがページにどのように適用されるかを判断するために、ブラウザは「これは title
クラスの要素で、親要素の n 番目の子である親要素が box
クラスの要素なのか」を判断しなければなりません。ブラウザでこれを把握するまでに時間がかかることがあります。これを簡素化するために、セレクタをより具体的なクラス名に変更できます。
.final-box-title {
/* styles */
}
これらの置換クラス名は不自然に見えるかもしれませんが、ブラウザの処理が大幅に簡素化されます。たとえば、以前のバージョンでは、ブラウザが要素がそのタイプの最後であることを認識するには、まず他のすべての要素についてすべてを把握し、その後に続く要素が nth-last-child
であるかどうかを判断する必要がありました。クラス名のみに基づいてセレクタを要素にマッチングする場合よりも、計算コストが大幅に増大する可能性があります。
スタイル設定する要素の数を減らす
パフォーマンスに関するもう一つの考慮事項は、要素の変更時に実行する必要がある作業量です。これは多くの場合、セレクタの複雑さよりも重要です。
一般的に、計算された要素スタイルの計算における最悪の場合、要素の数にセレクタ数を乗算します。これは、ブラウザは各要素を少なくとも 1 回、すべてのスタイルに対してチェックして、一致するかどうかを確認する必要があるためです。
スタイル計算では、ページ全体を無効にする代わりに、少数の要素を直接対象にできます。ブラウザは必ずしも変更によって影響を受ける可能性のあるすべての要素をチェックする必要はないため、最近のブラウザでは、これは大きな問題にはなりません。一方、古いブラウザはこのような処理に対して最適化されていない場合があります。可能な場合は、無効化される要素の数を減らす必要があります。
スタイル再計算コストを測定する
ブラウザでスタイルの再計算のコストを確認する方法はいくつかあります。それぞれ、開発環境のブラウザで測定するのか、ウェブサイトで実際のユーザーがこの処理に要する時間を測定するのかによって異なります。
Chrome DevTools でスタイル再計算コストを測定する
スタイル再計算のコストを測定する方法の一つは、Chrome DevTools のパフォーマンス パネルを使用することです。開始する手順は次のとおりです。
- DevTools を開きます。
- [パフォーマンス] タブに移動します。
- [セレクタの統計情報] チェックボックスをオンにします(省略可)。
- [Record] をクリックします。
- ページを操作する。
記録を停止すると、次のような画像が表示されます。
上部のストリップは、フレームレートもプロットするミニチュア フレームグラフです。アクティビティがストリップの下部に近いほど、ブラウザによるフレームのペイントが速くなります。炎のグラフが上部で平坦になり、その上に赤いバーが表示されている場合は、長時間実行されているフレームの原因となる処理があります。
スクロールなどのインタラクション中に長時間実行されているフレームは、詳しく調べる価値があります。大きな紫色のブロックが表示された場合は、アクティビティを拡大し、[Recalculate Style] というラベルが付いた作業を選択して、コストがかかる可能性のあるスタイル再計算作業の詳細を確認します。
イベントをクリックすると、コールスタックが表示されます。レンダリング処理がユーザー操作によって発生した場合は、スタイル変更をトリガーした JavaScript を呼び出します。また、変更が影響する要素の数(この例では 900 要素以上)と、スタイル計算にかかった時間も表示されます。この情報を使用して、コード修正の試行を開始できます。
トレースを行う前に [パフォーマンス パネル] の設定で [セレクタの統計情報] チェックボックスをオンにしていた場合、トレースのパネルの下部に同じ名前のタブが追加されます。
このパネルには、各セレクタの相対的なコストに関する有用なデータが表示されるので、コストの高い CSS セレクタを特定できます。
詳細については、CSS セレクタの統計情報のドキュメントをご覧ください。
実際のユーザーのスタイル再計算費用を測定する
ウェブサイトの実際のユーザーに対してスタイルの再計算が行われるまでにかかる時間を把握したい場合は、Long Animation Frames API を使用すると必要なツールが提供されます。この API のデータは、スタイルの再計算時間など、web-vitals
JavaScript ライブラリに追加されています。
インタラクションの表示遅延がページの INP の主な原因と思われる場合は、その時間のうち、ページのスタイルの再計算に費やされている時間の割合を把握する必要があります。詳細については、フィールドでスタイルの再計算時間を測定する方法をご覧ください。