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

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

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

スタイル計算の最初の部分は、マッチング セレクタのセットを作成することです。これは基本的に、どのクラス、疑似セレクタ、ID が特定の要素に適用されるかをブラウザが判断するためのものです。

プロセスの後半では、一致するセレクタからすべてのスタイルルールを取得し、要素の最終スタイルを特定します。

まとめ

  • スタイル計算のコストを削減することでインタラクションのレイテンシを短縮する方法。
  • セレクタの複雑さを軽減する。クラス中心の方法論(BEM など)を使用する。
  • スタイル計算を計算する必要がある要素の数を減らします。

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

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

インタラクションの重要な要素は、次のフレームをペイントするのにかかる時間です。次のフレームを表示するために行われるレンダリング処理は、レイアウト、ペイント、合成処理の直前に行われるページスタイルの計算など、多くの部分で構成されます。この記事ではスタイル計算のコストだけに焦点を当てていますが、重要なのは、インタラクションに固有のレンダリング フェーズの一部を減らすことで、スタイル計算を含めた全体的なレイテンシを削減できることを強調することです。

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

最も単純なケースでは、クラスを使用するだけで CSS 内の要素を参照できます。

.title {
  /* styles */
}

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

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

これらのスタイルがページにどのように適用されるかを知るには、ブラウザは「これは、親がたまたま n 番目の子と box のクラスを持つ 1 つの要素を持つ、title というクラスの要素か」と効果的に確認する必要があります。これを調べるには、使用するセレクタや対象のブラウザによっては、時間がかかります。この場合、セレクタの意図する動作をクラスに変更することもできます。

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

クラス名を変えることもできますが、ブラウザにとってはジョブがはるかにシンプルになりました。以前のバージョンでは、たとえば、要素がその型の最後のものであることを知るために、ブラウザはまず他のすべての要素についてすべてを把握して、その後に nth-last-child に該当する要素があるかどうかを知る必要があります。これは、クラスが一致するため、単にセレクタを要素にマッチングさせるよりもコストが高くなる可能性があります。

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

パフォーマンスに関するもう一つの考慮事項は、通常、多くのスタイルの更新でより重要な要素ですが、要素が変更されたときに実行する必要がある作業量の多さです。

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

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

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

スタイルの再計算のコストを測定する 1 つの方法は、Chrome DevTools のパフォーマンス パネルを使用することです。まず、DevTools を開き、[パフォーマンス] タブに移動して [記録] をクリックし、ページを操作します。録画を停止すると、下の画像が表示されます。

スタイル計算を示す DevTools。

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

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

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

スタイルの再計算作業によって影響を受ける要素の量などの重要な情報を含む、長時間実行されるスタイル計算の詳細を取得する。

このグラブでは、25 ミリ秒を少し超えて長時間実行されているスタイル再計算処理が行われています。

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

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

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

.list {
  /* Styles */
}

.list__list-item {
  /* Styles */
}

上記のような、最後の子に対して特別な処理を行う修飾子が必要な場合は、以下のように追加します。

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

CSS を効果的に整理する方法を探している場合、BEM は、構造の観点からも、方法論によって促進されるスタイル ルックアップの簡素化の点でも良い出発点となります。

BEM に適さない場合、CSS にアプローチする方法は他にもありますが、そのアプローチのエルゴノミクスと併せて、パフォーマンスに関する考慮事項も評価する必要があります。

リソース

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