スタイル計算の範囲と複雑さを軽減

JavaScript は多くの場合、視覚的な変化を引き起こす要因となります。スタイル操作によってこれらの変更を直接行うこともあれば、データの検索や並べ替えなど、計算によって視覚的な変化が生じることもあります。タイミングの悪い JavaScript や長時間実行される JavaScript は、パフォーマンスの問題の一般的な原因となる可能性があるため、可能な限りその影響を最小限に抑える必要があります。

スタイルの計算

要素の追加と削除、属性やクラスの変更、アニメーションの再生などによって DOM を変更すると、ブラウザは要素のスタイルを再計算し、多くの場合はページの一部または全体のレイアウトを再計算します。このプロセスはスタイル計算と呼ばれます。

ブラウザは、まずマッチング セレクタのセットを作成してスタイルの計算を開始し、特定の要素に適用するクラス、疑似セレクタ、ID を決定します。次に、マッチング セレクタからのスタイルルールを処理し、要素の最終的なスタイルを特定します。

スタイルの再計算時間とインタラクション レイテンシ

Interaction to Next Paint(INP)は、ユーザー入力に対するページの全体的な応答性を評価する、ユーザー中心のランタイム パフォーマンス指標です。ユーザーがページを操作してから、対応する視覚的な更新をユーザー インターフェースに表示する次のフレームをブラウザがペイントするまでのインタラクション レイテンシを測定します。

インタラクションの重要な要素は、次のフレームを描画するのにかかる時間です。次のフレームを表示するために行われるレンダリング処理は、レイアウト、ペイント、合成処理の直前に行われるページスタイルの計算など、多くの部分で構成されます。このページでは、スタイル計算の費用に焦点を当てますが、インタラクションに関連するレンダリング フェーズの部分を減らすと、スタイル計算を含む合計レイテンシも短縮されます。

セレクタの複雑さを軽減する

セレクタ名をシンプルにすることで、ページのスタイル計算を高速化できます。最も単純なセレクタは、クラス名だけで CSS の要素を参照します。

.title {
  /* styles */
}

ただし、プロジェクトが拡大するにつれ、より複雑な CSS が必要になる可能性があります。また、次のようなセレクタが必要になることもあります。

.box:nth-last-child(-n+1) .title {
  /* styles */
}

これらのスタイルがページにどのように適用されるかを判断するには、ブラウザは実質的に「これは、クラスが box の -nth-plus-1 の子要素を親が持つ title クラスの要素か?」と確認する必要があります。この判断には、使用するセレクタや対象のブラウザによっては、時間がかかることがあります。これを簡単にするために、セレクタを単なるクラス名に変更できます。

.final-box-title {
  /* styles */
}

このように置換されたクラス名は不自然に思えるかもしれませんが、ブラウザの処理はとても簡単になります。たとえば、以前のバージョンでは、ある要素がその型の最後のものであることをブラウザが認識するには、まず他のすべての要素についてすべてを把握して、その後に nth-last-child になり得る要素があるかどうかを判断する必要があります。この場合、クラスが一致するという理由だけでセレクタと要素を照合するよりも、計算コストがはるかに高くなる可能性があります。

スタイルを設定する要素の数を減らす

セレクタの複雑さよりも重要なパフォーマンスに関するもう一つの考慮事項は、要素の変更時に発生する必要がある作業量です。

一般的に、計算された要素のスタイルを計算するための最悪のケースのコストは、要素の数にセレクタの数を掛けたものになります。これは、ブラウザは、すべてのスタイルに対して各要素を少なくとも 1 回チェックして、一致するかどうかを確認する必要があるためです。

スタイル計算では、ページ全体を無効にする代わりに、いくつかの要素を直接対象にできます。最新のブラウザでは、変更の影響を受けるすべての要素を常にチェックする必要がないため、この問題はあまり発生しない傾向があります。一方、古いブラウザは、そのようなタスクに常に最適化されているとは限りません。可能であれば、無効化される要素の数を減らす必要があります。

スタイルの再計算費用を測定する

スタイルの再計算のコストを測定する 1 つの方法は、Chrome DevTools のパフォーマンス パネルを使用する方法です。手順は次のとおりです。

  1. DevTools を開きます。
  2. [パフォーマンス] タブに移動します。
  3. [Record] をクリックします。
  4. ページを操作する。

録画を停止すると、次のような画像が表示されます。

スタイル計算を示す DevTools。
スタイルの計算を示す DevTools レポート

上部のストリップは、1 秒あたりのフレーム数をプロットする小さなフレームチャートです。アクティビティがストリップの下部に近づくほど、ブラウザによるフレームの描画が速くなります。フレームチャートの上部が水平になり、上部に赤いバーがある場合は、長時間実行フレームの原因となっている作業があります。

Chrome DevTools に入力されたパフォーマンス パネルのアクティビティ サマリーで、Chrome DevTools の問題領域にズームインします。
DevTools のアクティビティ サマリーの長時間実行フレーム

スクロールなどの操作中に長時間実行されるフレームは、詳しく調べる価値があります。大きな紫色のブロックが表示されている場合は、アクティビティにズームインして [Recalculate Style] というラベルの付いた作業を選択し、コストがかかる可能性があるスタイルの再計算作業に関する詳細情報を取得します。

スタイル再計算処理の影響を受ける要素の量などの重要な情報を含む、長時間実行スタイル計算の詳細を取得する。
DevTools の概要で、25 ms 強かかる、長時間実行のスタイルの再計算。

イベントをクリックすると、そのコールスタックが表示されます。レンダリング処理がユーザー操作に起因する場合は、スタイルの変更をトリガーした JavaScript が呼び出されます。また、変更の影響を受ける要素の数(この場合は 900 個強)と、スタイルの計算にかかった時間も表示されます。この情報を基に、コード内の修正方法を探すことができます。

ブロック、要素、修飾子を使用する

BEM(Block、Element、Modifier)などのコーディング手法は、パフォーマンス上のメリットに合わせてセレクタに組み込まれています。BEM では、すべてに 1 つのクラスを含めることを推奨しています。階層が必要な場合は、その階層もクラス名に組み込むことをおすすめします。

.list {
  /* Styles */
}

.list__list-item {
  /* Styles */
}

最後の子の例のような修飾子が必要な場合は、次のように追加できます。

.list__list-item--last-child {
  /* Styles */
}

BEM は、構造の観点からも、スタイル検索が簡素化されるため、CSS を整理するための出発点として適しています。

BEM が気に入らない場合は、CSS にアプローチする方法が他にもありますが、開始する前にパフォーマンスとエルゴノミクスを評価する必要があります。

リソース

Unsplash より、Markus Spiske のヒーロー画像。