CSSの新しいプロパティ「content-visibility」レンダリングのパフォーマンスが向上する

Webページのパフォーマンスを向上させることは、CSSでも可能です。去年の暮れからChromeで利用できるようになったCSSの新しいプロパティで、2021年現在Edge, Operaにもサポートされています。

読み込みパフォーマンスを向上させるために最も効果があるCSSの新しいプロパティcontent-visibilityについて紹介します。

レンダリングのパフォーマンスを劇的に向上させる、CSSの新しいプロパティ「content-visibility」

content-visibility: the new CSS property that boosts your rendering performance
by Una Kravets, Vladimir Levin

下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。

はじめに

content-visibilityプロパティ(Chromium 85でリリース)は、ページの読み込みパフォーマンスを向上させるために最も影響があるCSSの新しいプロパティの1つかもしれません。

content-visibilityを使用すると、ユーザーエージェントがレイアウトやペイントを含む要素のレンダリング作業を必要になるまでスキップさせることができます。そして、レンダリングがスキップされるので、コンテンツの大部分がスクリーン外にある場合、content-visibilityプロパティを使用することで、初めて訪れたユーザーでも読み込みが格段に速くなります。さらに、コンテンツとのインタラクションも速くなり、非常に優れています。

サイトのキャプチャ

左: 今までのページ、右: content-visibilityを使用したページ

このデモでは特定の塊(チャンク化)にしたコンテンツエリアにcontent-visibility: auto;を適用すると、初期ロード時のレンダリング性能が7倍に向上しました。

content-visibilityのサポート状況

content-visibilityは、CSS Containment Module Level 2に定義されています。

【訳者注】
2021年現在、CSS Containmentはほとんどのブラウザでサポートされていますが、content-visibilityプロパティはChrome, Edge, Operaでサポートされています。

サイトのキャプチャ

content-visibilityのサポート状況

CSS Containmentとは

CSS Containmentの重要かつ包括的な目標は、ページの他の部分からDOMサブツリーを予測可能に分離することにより、Webコンテンツのレンダリングのパフォーマンスを向上させることです。

デベロッパーはページのどの部分がコンテンツのセットとしてカプセル化されているかをブラウザに伝えることができるため、ブラウザはサブツリーの外部の状態を考慮することなくコンテンツについて予測することができます。

コンテンツのどの部分(サブツリー)に孤立したコンテンツが含まれているかを知ることで、ブラウザはレンダリングの最適化を判断できるようになります。

CSS Containmentには4つのタイプがあり、それぞれがcontainプロパティの潜在的な値で、スペースで区切られた値のリストにまとめることができます。

  • size
    サイズを封じ込めることで、要素の子孫を調べることなく要素のボックスをレイアウトできるようになります。これは要素のサイズだけが必要な場合、子孫のレイアウトをスキップできる可能性があることを意味します。
  • layout
    レイアウトの封じ込めとは、子孫がページ上の他のボックスの外部レイアウトに影響を与えないことを意味します。これにより、他のボックスをレイアウトしたいだけの場合に、子孫のレイアウトをスキップすることができます。
  • style
    スタイルの封じ込めは、子孫以外にも影響をあたえる可能性のあるプロパティが要素からエスケープされないことが保証されます(カウンターなど)。これにより、他の要素のスタイルを計算したいだけの場合に、子孫のスタイル計算をスキップできる可能性があります。
  • paint
    ペイントの封じ込めは、封じ込められたボックスの子孫がその境界外に表示されないようにします。目に見えていない要素をオーバーフローさせることはできず、要素がスクリーン外にある場合やその他の方法で非表示の場合は、その子孫も表示されません。これにより、要素がスクリーン外にある場合に子孫のペイントをスキップできる可能性があります。

content-visibilityでレンダリングをスキップさせる

ブラウザの最適化は適切なセットが指定された場合にのみ有効になることがあるため、使用する包含値を見極めるのは難しいかもしれません。値を調整して何が最適なのかを確認するか、content-visibilityと呼ばれるプロパティを使用して必要な包含を自動的に適用することができます。content-visibilityを使用することで、デベロッパーとして最小限の労力でブラウザが提供できる最大のパフォーマンス向上を確実に得ることができます。

content-visibilityプロパティはいくつかの値を受け入れますが、パフォーマンスをすぐに向上させるのはautoです。content-visibility: auto;を定義した要素はlayout, style, paintを包含されます。要素がスクリーン外にある場合(他ではユーザーに関連しない場合は、関連する要素はサブツリーにフォーカスや選択がある要素のことです)、sizeも包含されます(そして、そのコンテンツのペイントヒットテストを停止します)。

このことは何を意味すると思いますか?
つまり、要素がスクリーン外にある場合、その子孫はレンダリングされません。ブラウザは要素のコンテンツを一切考慮せずに要素のサイズを決定し、そこで停止します。要素のサブツリーのスタイルやレイアウトなど、ほとんどのレンダリングはスキップされます。

要素がビューポートに近づくと、ブラウザはsize包含を追加せず、要素のコンテンツのペイントとヒットテストを開始します。これにより、ユーザーに見られるギリギリのタイミングでレンダリング作業を行うことができます。

content-visibilityを使用した例

下記のデモページでは、チャンク化されたエリアにcontent-visibility: auto;を適用しました。その結果、レンダリング時間が232ミリ秒から30ミリ秒になりました。

See the Pen
Content-visibility Demo: Base (Content Visibility on Grids)
by Una Kravets (@una)
on CodePen.

デモページは、いくつかの写真とテキストを含むストーリーのセットで構成されています。一般的なブラウザで表示した時に何が起こるかを下記に示します。

  1. ページの一部は、必要なリソースとともにネットワークからダウンロードされます。
  2. ブラウザはコンテンツがユーザーに表示されるかどうかを考慮せずに、ページのすべてのコンテンツのスタイルを設定し、レイアウトします。
  3. ブラウザはページとリソースのすべてがダウンロードされるまで、Step 1に戻ります。

Step 2では、ブラウザはすべてのコンテンツを処理して、変更された可能性のあるものを探します。新しい要素のスタイルとレイアウトを更新し、新しい更新の結果としてシフトした可能性のある要素も更新します。これがレンダリングの作業です。そして、これには時間がかかります。

サイトのキャプチャ

レンダリングされたデモページ

では、ページ上の個々のストーリーにcontent-visibility: auto;を設定した場合にどうなるか考えてみましょう。基本的にステップのループは同じです。ブラウザはページのチャンクをダウンロードして、レンダリングします。しかし、違いはStep 2での作業量にあります。

content-visibilityを設定すると、現在ユーザーに表示されている(スクリーン上に表示されている)すべてのコンテンツのスタイルとレイアウトを行います。ただし、完全にスクリーン外にあるストーリーを処理する場合、ブラウザはレンダリング作業をスキップして、要素ボックス自体のスタイルとレイアウトのみを行います。

このページを読み込んだ場合のパフォーマンスは、スクリーン上のストーリー全体とスクリーン外のストーリーごとに空のボックスが含まれているかのようになります。これにより、パフォーマンスが大幅に向上し、読み込み時のレンダリングコストが50%以上削減されることが期待されます。上記のデモでは、レンダリング時間が232ミリ秒から30ミリ秒に向上し、パフォーマンスが7倍も向上しました。

このメリットを享受するために必要な作業は何でしょうか?
コンテンツをセクションにチャンク化(塊にまとめる)することです。

サイトのキャプチャ

デモページ

.storyのclassを適用したセクションをチャンク化し、content-visibility: auto;を定義します。

コードは、下記の通りです。

コンテンツが表示されたり表示されなくなったりすると、必要に応じてレンダリングが開始されたり停止されたりすることに注意してください。ただし、レンダリング作業は可能な限り保存されるので、ブラウザが同じコンテンツを何度もレンダリングして再レンダリングする必要があるということではありません。

contains-intrinsic-sizeで要素の自然なサイズを指定する

content-visibilityの潜在的な利点を実現するために、ブラウザはサイズの包含を適用してコンテンツのレンダリング結果が要素のサイズに影響を与えないようにする必要があります。これは要素が空であるかのようにレイアウトされることを意味します。通常のブロックレイアウトで要素の高さが定義されていない場合、要素の高さは0になります。

ただし、各ストーリーの高さが0ではないことによりスクロールバーの長さがずれるので、これは理想的ではないかもしれません。

しかし、ありがたいことにCSSはcontain-intrinsic-sizeという別のプロパティがあり、要素がサイズの包含によって影響を受けている場合に要素の自然なサイズを効果的に指定します。上記のコードでは、セクションの高さと幅の推定値として1000pxに設定しています。

これは「本質的なサイズ(intrinsic size)」のサイズの子が1つあるかのようにレイアウトされることを意味し、サイズのないdivがスペースを占めることになります。つまり、contain-intrinsic-sizeはレンダリングされたコンテンツの代わりにプレースホルダーサイズとして機能します。

content-visibility: hidden;でコンテンツを非表示にする

キャッシュされたレンダリング状態の利点を活用しながら、コンテンツが表示されているかどうかに関係なくレンダリングされない状態を維持したい場合はどうすればよいでしょうか?

content-visibility: hidden;を使用します。

このCSSはcontent-visibility: auto;がスクリーン外で行うのと同じように、レンダリングされていないコンテンツとキャッシュされたレンダリング状態のすべての利点を提供します。ただし、autoとは異なり、自動的にスクリーン上でレンダリングを開始することはありません。

このCSSにより、その要素のコンテンツを非表示にしたり、後で素早く非表示を解除できるようになり、より詳細なコントロールが可能になります。

コンテンツを非表示にする他のプロパティと比較してみましょう。

  • display: none;
    要素を非表示にし、そのレンダリング状態を破棄します。つまり、要素を非表示にすることは、同じコンテンツの新しい要素をレンダリングするのと同じくらいコストがかかります。
  • visibility: hidden;
    要素を非表示にし、そのレンダリング状態を維持します。要素(とそのサブツリー)はページ上で幾何学的なスペースを占め、クリックすることができるため、ドキュメントから要素を実際に削除しているわけではありません。また、非表示にしても必要に応じていつでもレンダリング状態を更新します。

一方、content-visibility: hidden;はレンダリング状態を維持しながら要素を非表示にするため、変更が必要な場合でも要素が再び表示されたときのみ発生します(つまり、content-visibility: hidden;プロパティは削除されます)。

content-visibility: hidden;の優れた使用例は、高度な仮想スクローラーを実装し、レイアウトを測る場合です。

終わりに

content-visibilityとCSS Containment Specは、CSSファイルのパフォーマンスを向上させることを意味します。

各プロパティの詳細については下記を参照してください。

sponsors

top of page

©2021 coliss