画面外コンテンツのレンダリングをスキップすることで、初期読み込み時間を短縮します。
Chromium 85 でリリースされる content-visibility
プロパティは、ページ読み込みのパフォーマンスを向上させるうえで特に効果的な新しい CSS プロパティの一つです。content-visibility
を使用すると、ユーザー エージェントは、必要になるまでレイアウトや描画を含む要素のレンダリング作業をスキップできます。レンダリングはスキップされるため、コンテンツの大部分が画面外にある場合、content-visibility
プロパティを使用すると、ユーザーの初期読み込みが大幅に高速化されます。また、画面上のコンテンツに対する操作も速くなります。かなりスタイリッシュです。
ブラウザ サポート
content-visibility
は、CSS Containment Spec 内のプリミティブに依存します。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
などのスタイル機能を持つ landmark 要素もユーザー補助ツリーに表示されます。これは、ビューポートに入るまでブラウザはスタイルをレンダリングしないためです。これらがユーザー補助ツリーに表示され、見づらくなるのを防ぐために、aria-hidden="true"
も追加してください。
例: 旅行ブログ
旅行ブログには通常、数枚の写真と説明テキストを含む一連の記事が掲載されます。一般的なブラウザで旅行ブログに移動すると、次のようになります。
- ページの一部が、必要なリソースとともにネットワークからダウンロードされます。
- このブラウザは、コンテンツがユーザーに表示されるかどうかを考慮せずに、ページのコンテンツのすべてのスタイルを設定して配置します。
- すべてのページとリソースがダウンロードされるまで、ブラウザはステップ 1 に戻ります。
ステップ 2 では、ブラウザがすべてのコンテンツを処理し、変更された点を探します。新しい要素のスタイルとレイアウト、および新しい更新の結果としてシフトした可能性のある要素を更新します。これはレンダリング処理です。これには時間がかかります。
ブログ内の各記事に content-visibility: auto
を付けた場合について考えてみましょう。一般的なループは同じです。ブラウザがページの一部をダウンロードし、レンダリングします。ただし、ステップ 2 で行う作業量が異なります。
コンテンツの公開設定では、現在ユーザーに表示されているすべてのコンテンツ(画面上に表示されている)のスタイルとレイアウトを設定します。ただし、完全に画面外のストーリーを処理する場合、ブラウザはレンダリング作業をスキップし、要素ボックス自体のスタイルとレイアウトのみを行います。
このページの読み込みのパフォーマンスは、あたかも画面上に完全なストーリーと、画面外の各記事の空のボックスが含まれているかのように動作します。パフォーマンスが大幅に向上し、読み込みのレンダリング コストが 50% 以上削減されることが見込まれます。この例では、レンダリング時間が 232 ミリ秒から 30 ミリ秒に向上しています。パフォーマンスが 7 倍向上しました。
これらのメリットを享受するためには、どのような作業が必要ですか。まず、コンテンツをいくつかのセクションに分割します。
次に、各セクションに以下のスタイルルールを適用します。
.story {
content-visibility: auto;
contain-intrinsic-size: 1000px; /* Explained in the next section. */
}
contain-intrinsic-size
で要素の自然なサイズを指定する
content-visibility
の潜在的なメリットを実現するには、ブラウザでサイズの包含を適用して、コンテンツのレンダリング結果が要素のサイズに影響しないようにする必要があります。つまり、要素は空であるかのようにレイアウトされます。通常のブロック レイアウトで要素の高さが指定されていない場合、要素の高さは 0 になります。
スクロールバーのサイズはシフトし、各ストーリーの高さがゼロ以外の値であることに依存するため、これは理想的ではないかもしれません。
幸いなことに、CSS には contain-intrinsic-size
という別のプロパティがあります。このプロパティは、サイズがサイズ制限の影響を受ける場合の要素の自然なサイズを効果的に指定します。この例では、セクションの高さと幅の推定値として 1000px
に設定します。
つまり、「本質的サイズ」のディメンションの子が 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 ファイルのパフォーマンスが大幅に向上します。これらのプロパティの詳細については、以下をご覧ください。