画面外コンテンツのレンダリングをスキップすることで、初期読み込み時間を改善します。
Chromium 85 でリリースされた content-visibility
プロパティは、ページ読み込みのパフォーマンスを向上させる新しい CSS プロパティの 1 つです。content-visibility
を使用すると、ユーザー エージェントは、必要になるまで、要素のレンダリング作業(レイアウトやペイントなど)をスキップできます。レンダリングがスキップされるため、コンテンツの大部分が画面外にある場合、content-visibility
プロパティを使用すると、最初のユーザー読み込みが大幅に高速化されます。また、画面上のコンテンツをより迅速に操作できます。とても便利です。
ブラウザ サポート
content-visibility
は CSS Containment 仕様内のプリミティブに依存します。現在のところ、content-visibility
は Chromium 85 でのみサポートされています(Firefox では「プロトタイピングに値する」と考えられます)が、Containment Spec は最新のブラウザでサポートされています。
CSS の封じ込め
CSS コンテインメントの主要な目標は、ページの他の部分から DOM サブツリーを予測可能な方法で分離することで、ウェブ コンテンツのレンダリング パフォーマンスを改善できるようにすることです。
基本的に、デベロッパーはページのどの部分をコンテンツのセットとしてカプセル化するかをブラウザに伝えることができるため、ブラウザはサブツリーの外部の状態を考慮しなくてもコンテンツについて推測できます。コンテンツのどの部分(サブツリー)に分離されたコンテンツが含まれているかを知ることで、ブラウザはページ レンダリングの最適化を判断できます。
CSS コンテインメントには 4 つのタイプがあり、それぞれ contain
CSS プロパティに指定できる値で、スペース区切りの値リストにまとめることができます。
size
: 要素にサイズを含めると、子孫を調べることなく、要素のボックスをレイアウトできるようになります。つまり、必要なのは要素のサイズだけである場合、子孫のレイアウトをスキップできる可能性があります。layout
: レイアウト包含とは、子孫がページ上の他のボックスの外部レイアウトに影響しないことを意味します。これにより、他のボックスをレイアウトするだけで、子孫のレイアウトをスキップできる可能性があります。style
: スタイルの包含により、子孫以外にも影響を与える可能性があるプロパティ(カウンタなど)がエスケープされないようにします。これにより、他の要素のスタイルを計算するだけの場合は、子孫のスタイル計算をスキップできます。paint
: 包含ペイントにより、包含ボックスの子孫が境界外に表示されないことが保証されます。要素を目に見える形ではみ出すことはできません。また、要素が画面外にある場合や表示されない場合、その子孫も表示されません。これにより、要素が画面外にある場合に、子孫のペイントをスキップする可能性があります。
content-visibility
でレンダリング処理をスキップする
ブラウザの最適化は適切なセットが指定されている場合にのみ有効になるため、どの包含値を使用するか判断するのが難しい場合があります。値をいろいろ試して、最も効果的なものを確認できます。また、content-visibility
という別の CSS プロパティを使用して、必要な包含要素を自動的に適用することもできます。content-visibility
を使用すると、デベロッパーが最小限の労力でブラウザで実現できる最大限のパフォーマンス向上を実現できます。
content-visibility プロパティには複数の値を指定できますが、auto
を使用すると、すぐにパフォーマンスが向上します。content-visibility: auto
が指定された要素は、layout
、style
、paint
のコンテインメントを取得します。要素が画面外にある場合(ユーザーに関係ない場合、関連する要素はサブツリーにフォーカスまたは選択がある要素です)、size
コンテインメントも取得されます(コンテンツのペイントとヒットテストが停止します)。
これはどういう意味ですか?つまり、要素が画面外にある場合、その子孫はレンダリングされません。ブラウザは、その内容を考慮せずに要素のサイズを決定し、そこで処理を終了します。要素のサブツリーのスタイルやレイアウトなど、レンダリングの大部分はスキップされます。
要素がビューポートに近づくと、ブラウザは size
コンテインメントを追加せず、要素のコンテンツのペイントとヒットテストを開始します。これにより、ユーザーに表示されるタイミングに合わせてレンダリング処理を実行できます。
ユーザー補助に関する注意事項
content-visibility: auto
の特長の 1 つは、画面外コンテンツがドキュメント オブジェクト モデル(visibility: hidden
とは異なり)で引き続き使用できることです。つまり、読み込みを待ったり、レンダリング パフォーマンスを犠牲にしたりすることなく、ページ上でコンテンツを検索し、そのコンテンツに移動できます。
ただし、その裏側には、display: none
や visibility: hidden
などのスタイル対象物を含むランドマーク要素は、画面外でもユーザー補助ツリーに表示されます。これは、ブラウザがビューポートに入るまでこれらのスタイルをレンダリングしないためです。これらがユーザー補助ツリーに表示されず、煩雑になるのを防ぐため、aria-hidden="true"
も必ず追加してください。
例: 旅行ブログ
旅行ブログには通常、数枚の画像と説明テキストを含む一連の記事が掲載されています。一般的なブラウザで旅行ブログに移動すると、次のようになります。
- ページの一部が、必要なリソースとともにネットワークからダウンロードされます。
- ブラウザは、コンテンツがユーザーに表示されているかどうかを考慮せずに、ページのすべてのコンテンツをスタイル設定してレイアウトします。
- ページとリソースがすべてダウンロードされるまで、ブラウザはステップ 1 に戻ります。
ステップ 2 では、ブラウザはすべてのコンテンツを処理して、変更された部分がないかどうかを調べます。新しい要素のスタイルとレイアウトに加え、新しい更新の結果としてシフトした要素も更新されます。これがレンダリング処理ですこれには時間がかかります。
次に、ブログ内の各記事に content-visibility: auto
を設定するとどうなるかを考えてみましょう。一般的なループは同じです。ブラウザがページのチャンクをダウンロードしてレンダリングします。ただし、ステップ 2 で実行される処理の量が異なります。
content-visibility は、現在ユーザーに表示されている(画面上にある)すべてのコンテンツのスタイルとレイアウトを設定します。ただし、完全に画面外にあるストーリーを処理する場合、ブラウザはレンダリング作業をスキップし、要素ボックス自体のスタイルとレイアウトのみを設定します。
このページの読み込みパフォーマンスは、画面上にストーリー全体が表示され、画面外のストーリーがそれぞれ空白のボックスで表示されているかのように行われます。これによりパフォーマンスが大幅に向上し、読み込みのレンダリング コストと比べて 50% 以上削減が予想されます。この例では、レンダリング時間が 232 ミリ秒から 30 ミリ秒に短縮されています。パフォーマンスが 7 倍に向上します。
これらのメリットを享受するために必要な作業は何ですか?まず、コンテンツをセクションに分割します。
次に、以下のスタイルルールをセクションに適用します。
.story {
content-visibility: auto;
contain-intrinsic-size: 1000px; /* Explained in the next section. */
}
contain-intrinsic-size
を使用して要素の自然なサイズを指定する
content-visibility
の潜在的なメリットを実現するには、ブラウザでサイズの包含を適用して、コンテンツのレンダリング結果が要素のサイズに影響しないようにする必要があります。つまり、要素が空であるかのように配置されます。通常のブロック レイアウトで要素の高さが指定されていない場合、高さは 0 になります。
スクロールバーのサイズが移動し、各記事の高さが 0 以外であることに依存するため、この方法は理想的ではない場合があります。
幸いなことに、CSS には別のプロパティ contain-intrinsic-size
が用意されています。このプロパティは、要素がサイズ包含の影響を受ける場合に、要素の自然なサイズを実質的に指定します。この例では、セクションの高さと幅の推定値として 1000px
に設定しています。
つまり、「intrinsic-size」ディメンションの子が 1 つあるかのようにレイアウトされるため、サイズ設定されていない div がスペースを占有します。contain-intrinsic-size
は、レンダリングされたコンテンツの代わりにプレースホルダ サイズとして機能します。
Chromium 98 以降では、contain-intrinsic-size
に新しい auto
キーワードがあります。指定すると、ブラウザは最後にレンダリングされたサイズを記憶し、デベロッパーが指定するプレースホルダ サイズの代わりにそのサイズを使用します。たとえば、contain-intrinsic-size: auto 300px
を指定した場合、要素は各寸法で 300px
固有のサイズ設定で開始しますが、要素のコンテンツがレンダリングされると、レンダリングされた固有のサイズを保持します。その後のレンダリング サイズの変更も保存されます。つまり、content-visibility: auto
が適用された要素をスクロールした後、画面外にスクロールすると、自動的に最適な幅と高さが保持され、プレースホルダのサイズに戻されません。この機能は、ユーザーがページを探索するにつれて、時間の経過とともにサイズ予測を自動的に改善する無限スクローラーで特に役立ちます。
content-visibility: hidden
でコンテンツを非表示にする
キャッシュされたレンダリング状態のメリットを活用しながら、画面上に表示されているかどうかにかかわらず、コンテンツをレンダリングしないままにしたい場合はどうすればよいでしょうか。「content-visibility: hidden
」と入力します。
content-visibility: hidden
プロパティには、content-visibility: auto
が画面外で行う場合と同じ、レンダリングされていないコンテンツとキャッシュされたレンダリング状態のメリットがすべて備わっています。ただし、auto
とは異なり、画面上のレンダリングは自動的に開始されません。
これにより、より詳細な制御が可能になり、要素のコンテンツを非表示にして、後ですばやく再表示できるようになります。
これを、要素のコンテンツを非表示にする一般的な方法と比較します。
display: none
: 要素を非表示にし、レンダリング状態を破棄します。つまり、要素の再表示は、同じ内容の新しい要素をレンダリングするのと同じコストになります。visibility: hidden
: 要素を非表示にして、レンダリング状態を維持します。これによってドキュメントから要素が完全に削除されるわけではありません。その要素(およびサブツリー)は引き続きページの幾何学的スペースを占有し、クリックできるためです。また、非表示の場合でも、必要に応じてレンダリング状態を更新します。
一方、content-visibility: hidden
は、レンダリング状態を保持したまま要素を非表示にします。そのため、変更が必要な場合は、要素が再度表示されたとき(content-visibility: hidden
プロパティが削除されたとき)にのみ変更が行われます。
content-visibility: hidden
の優れたユースケースには、高度な仮想スクローラーを実装したり、レイアウトを測定したりする場合が挙げられます。また、シングルページ アプリケーション(SPA)にも適しています。非アクティブなアプリビューは、content-visibility: hidden
が適用された DOM 内に残して表示を防ぎますが、キャッシュされた状態は維持されます。これにより、ビューが再びアクティブになったときに、すばやくレンダリングできるようになります。
Interaction to Next Paint(INP)への影響
INP は、ページがユーザー入力に確実に応答できるかどうかを評価する指標です。レンダリング処理など、メインスレッドで大量の処理が発生すると、応答性に影響が出ることがあります。
特定のページのレンダリング作業を削減できれば、メインスレッドがユーザー入力により速く応答する機会が与えられます。これにはレンダリング作業が含まれます。また、必要に応じて content-visiblity
CSS プロパティを使用すると、レンダリング作業(特にレンダリングとレイアウト作業のほとんどが完了している起動時)を減らすことができます。
レンダリング処理の削減は、INP に直接影響します。content-visibility
プロパティを適切に使用しているページをユーザーが操作して画面外要素のレイアウトとレンダリングを遅らせようとすると、メインスレッドがユーザーに表示される重大な作業に応答する機会が与えられます。これにより、場合によってはページの INP が向上する可能性があります。
おわりに
content-visibility
と CSS Containment Spec により、CSS ファイルのパフォーマンスが大幅に向上します。これらのプロパティの詳細については、以下をご覧ください。