代表的な面接トピック

フロントエンド面接:レイアウトシフトを引き起こさずに content-visibility を使用する方法

フロントエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

コンテンツフィードに数百枚のカードがあり、初回レンダリングとスクロールの両方が遅く感じられます。content-visibility: auto を使用して画面外のレンダリングを遅延させる方法、contain-intrinsic-size がスクロールバーのジャンプを防止する仕組み、そして INP、レイアウトの安定性、アクセシビリティがリグレッションしていないことを検証する方法を説明してください。

質問と適したシナリオ

あなたは長いリスト、記事フィード、または管理画面ワークスペースを担当しています。DOMには数百ものコンテンツセクションが含まれており、初期状態ではわずかなカードしか表示されていませんが、ブラウザは画面外のサブツリーに対してもスタイル、レイアウト、ペイント処理を実行してしまいます。面接では、ページ内検索、フォーカス、スクリーンリーダーのアクセスを維持しながら、CSSコンテインメントを使用してメインスレッドのレンダリング作業を削減することが求められます。

ページは独立したカードやセクションに分割可能で、それらの高さにはばらつきがあるものの無限大ではなく、対象ブラウザは content-visibility: auto をサポートしていると仮定します。未対応のブラウザでもページは正しくレンダリングされなければなりません。

面接官が評価している点

  • 最適化を行う前に、ネットワーク、スクリプト、レンダリングのボトルネックを切り分けているか。
  • auto が暗示するレイアウト、スタイル、ペイントのコンテインメントに加え、画面外でのサイズコンテインメントを説明できるか。
  • スクロールバーのジャンプやCLSのリスクを引き起こすプレースホルダーサイズの誤差を予測できているか。
  • フォーカス、ページ内検索、スクリーンリーダー、DOM読み取りの動作と合わせてパフォーマンスを検証しているか。

不十分な回答はCSSプロパティを1つ提示するだけで終わります。優れた回答は、境界、サイズ設定戦略、強制レイアウト読み取りのコスト、フォールバック計画まで言及します。

回答前に明確にすべき質問

  1. 速度低下は初回レンダリング時、スクロール中、フィルタリング後、それともクリック後に発生していますか? 発生フェーズによって、レンダリング、スクリプト、ネットワークのどれを測定すべきかが決まります。
  2. カードの高さは一定ですか? ばらつきが大きいほど、実測値の採用や contain-intrinsic-size: auto によるレンダリング済みサイズの記憶が重要になります。
  3. 画面外のコンテンツも検索可能、フォーカス可能、支援技術への公開を維持する必要がありますか? もしそうなら、hiddendisplay: none の完全な代替にはなりません。
  4. スクロールや状態更新のたびにコードが offsetHeightgetBoundingClientRect() を読み取っていませんか? このような読み取りは、スキップされたレンダリング作業をクリティカルパスに引き戻す可能性があります。

30秒の回答フレームワーク

「まずPerformanceパネルを使用して、画面外のサブツリーのレンダリングがボトルネックであることを確認します。フィードを独立したセクションに分割し、重要でないセクションに content-visibility: auto を適用し、サイズコンテインメントによって空に見えないよう現実的な contain-intrinsic-size を指定します。次に、フォーカス、ページ内検索、支援技術の動作をテストし、レイアウトを強制するDOM読み取りを監査します。最後に、初回レンダリング、スクロール、INP、CLS、未サポート時のフォールバックを対照群と比較します。」

ステップごとの詳細な回答

1. レンダリング境界の確立

長いページを独立したセクションまたはカードに分割します。1つの境界内のレイアウト変更が関連のない領域に影響を与えないようにする必要があります。そうでなければ、コンテインメントが実際の依存関係を覆い隠し、不正確なレイアウトを引き起こす可能性があります。

css
.story {
  content-visibility: auto;
  contain-intrinsic-size: auto 720px;
}

セクションがビューポートから離れている場合、auto によりブラウザはその子孫要素のスタイル、レイアウト、ペイント処理の一部をスキップできます。ビューポートに近づくと、ブラウザはオンデマンドでそれをレンダリングします。DOM自体は保持されます。

2. サイズプレースホルダーの提供

画面外のセクションは、その中身を検査することなく一時的にサイズが決定されます。明示的な高さや組み込みサイズがない場合、非常に低い空のボックスのように見え、スクロールバーの長さやユーザーのスクロール位置が変動してしまいます。

contain-intrinsic-size: auto 720px は初期の推定値を提供します。レンダリング後、ブラウザは実際のサイズを記憶できます。推定値は適当な数値を設定するのではなく、本番のサンプリングデータから導出します。ばらつきが大きい場合は、コンテンツタイプごとにバケット化するか、データとともにサイズヒントを提供します。

3. auto、hidden、display none の区別

  • auto: 画面外のレンダリングをスキップし、ビューポート付近で再開します。コンテンツはDOMおよびアクセシビリティツリーに残ります。
  • hidden: レンダリング状態を保持しつつ常にレンダリングをスキップします。非アクティブなビューに適していますが、アクセシビリティ上の非表示ニーズすべてに適しているわけではありません。
  • display: none: レイアウトおよびレンダリング状態を破棄するため、再表示時にその状態の再構築が必要になります。

支援技術からコンテンツを見えないようにする必要がある場合は、セマンティクス上正しい aria-hidden を使用するかDOMから削除し、非表示領域にフォーカスが入らないようにしてください。

4. 最適化を無効化する読み取りの監査

content-visibility が適用されたセクションに対して頻繁にレイアウト読み取りを行うと、ブラウザにスキップされたサブツリーの早期計算を強制してしまいます。測定は必要なときだけ行い、書き込みの前に読み取りをバッチ処理し、同期的レイアウトをトリガーする読み取りと書き込みの交互実行を避けます。

js
requestAnimationFrame(() => {
  const height = card.getBoundingClientRect().height;
  card.style.setProperty('--measured-height', `${height}px`);
});

これはタイミングを示す例であり、汎用的な処方箋ではありません。読み取り処理を削除する前に、Performanceの記録を使用してそれがロングタスクを引き起こしていることを証明してください。

5. ユーザー向け指標による検証

最低限、以下をテストします:

  • 初回レンダリングと総レンダリング時間:画面外の作業が削減されたことを証明するため。
  • INPまたはクリック時のロングタスク:メインスレッドの負荷に余裕ができたか確認するため。
  • CLSとスクロール位置:プレースホルダーサイズによるジャンプを検出するため。
  • キーボードのTab移動、ページ内検索、スクリーンリーダー:auto のアクセシビリティ動作を検証するため。
  • 未対応ブラウザ:デフォルトの visible 挙動により全コンテンツが問題なくレンダリングされることを確認するため。

web.devの事例では、特定ページのレンダリングが232msから30msに短縮されました。これは一実験の結果として捉え、すべてのサイトに対する保証とはみなさないでください。結論は自身の対照群と適用群の測定結果から導き出す必要があります。

質の高い回答例

私はまず症状を「画面外コンテンツに対する過剰なレンダリング作業」と定義し、Performanceパネルでスタイル、レイアウト、またはペイントがメインスレッドを占有していることを確認します。確認後、ページを独立したセクションに分割し、content-visibility: auto を設定し、代表的なコンテンツから contain-intrinsic-size を見積もります。これにより、DOMと支援技術へのアクセスを損なうことなく、ブラウザは画面外の子孫要素のレンダリングをスキップできます。

初回レンダリング時間の短縮だけで満足しません。スクロールバーの挙動、フォーカス、ページ内検索を確認し、スクロール中や状態更新時にレイアウトを強制する getBoundingClientRectoffsetHeight の読み取りがないか探します。初回レンダリング、スクロール時のロングタスク、INP、CLS、実ユーザーデータを比較します。カードの高さが極端に異なる場合や、セクション間にレイアウト依存関係がある場合は、分割方針とサイズ設定戦略を見直します。未対応ブラウザはデフォルトの visible 挙動になるため、この最適化はプログレッシブエンハンスメントとして維持されます。

よくある間違い

  • 間違い → ページ全体に content-visibility: auto を追加する → 失敗する理由 → 不明確な境界によりレイアウトの依存関係が隠れ、原因特定が不可能になる → 修正策 → 独立したセクションに分割し、まずベースラインを記録する。
  • 間違い → 組み込みサイズ(intrinsic sizing)を省略する → 失敗する理由 → サイズコンテインメントによりセクションの高さが過小評価され、スクロールやCLSが悪化する → 修正策 → サンプルから推定し、実際のスクロール位置を観察する。
  • 間違い → hiddenaria-hidden の同義語として使用する → 失敗する理由 → 視覚的非表示とアクセシビリティのセマンティクスが異なる → 修正策 → プロダクトのセマンティクスに応じて、DOM削除、aria-hidden、またはコンテンツの保持を選択する。
  • 間違い → パフォーマンス関連のDOM読み取りをすべて削除する → 失敗する理由 → 必要な測定もあり、盲目的な削除は動作を壊す → 修正策 → プロファイリング記録を使用して実際にレイアウトを強制している読み取りを特定する。

フォローアップ質問と回答

カードの高さに5倍のばらつきがある場合、単一の組み込みサイズを使用しますか?

いいえ。単一の推定値では一部のカードが極端に短すぎたり高すぎたりしてしまいます。コンテンツタイプごとにバケット分けを行い、記憶されたレンダリング済みサイズを優先し、必要であればサーバーからサイズヒントを返します。CLSとスクロールの誤差を測定し、バケット化の追加複雑性に見合う価値があるかを判断します。

プロダクトの要件として画面外の動画デコードを完全に停止する必要がある場合、content-visibility だけで十分ですか?

いいえ。これは主にレンダリング作業を制御するものであり、メディアのライフサイクル管理を代替するものではありません。視認性が変わったときに動画を一時停止・再開し、そのロジックがスキップされたサブツリーのレイアウトを繰り返し読み取らないようにした上で、メディアポリシーをCSSの最適化とは別に測定します。

このプロパティをサポートしていないブラウザにはどのように配信しますか?

機能がこのプロパティに依存しないようにします。デフォルト値は visible であるため、ページは完全な状態を維持します。プログレッシブエンハンスメントと互換性のモニタリングを行い、古いブラウザに対してページネーションや仮想リストが代わりに必要かどうかを判断します。

成果がDOMサイズの縮小ではなく、このプロパティによるものであるとどのように証明しますか?

同じデータとDOM構造を維持し、ローカルまたはA/Bテストの対照群でCSSプロパティのみを切り替えます。レンダリング時間、ロングタスク、INP、CLSを記録します。ページネーション、画像の遅延読み込み、スクリプトのスケジューリングも同時に変更された場合、その結果を単一のプロパティの効果として帰属させることはできません。

公開情報ソース

関連する質問