代表的な面接トピック

フロントエンド面接:CSS scroll-state クエリはいつ JavaScript のスクロール検知を置き換えられるか?

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

質問

JavaScript でスクロールイベントをリッスンする代わりに、CSS scroll-state クエリを使用して sticky、snap、またはスクロール可能状態に応答するにはどうすればよいですか?

出題と適用される場面

面接の設問:ページ内に sticky 見出し、水平方向のスナップカード、さらにスクロール可能なコンテンツがあることを示すヒントがあります。スタイルを変更するタイミングをどのように判断しますか?CSS scroll-state クエリと JavaScript のスクロールリスナーを比較し、ブラウザがサポートしていない場合にどう対応するかを説明してください。

境界線となるのは宣言的なスタイリングです。見出しが固定されたときにシャドウを追加する、スナップされたカードをハイライトする、またはコンテンツがさらにスクロール可能なときにヒントを表示するといったケースです。ビジネス状態、複雑なジェスチャー、正確なスクロール進捗、サーバーへのレポート送信は、依然として JavaScript の領域です。MDN では、scroll-state クエリのファミリとして scrollable、scrolled、snapped、stuck の 4 つが説明されています。

面接官が評価している点

面接官は、状態のタイプに応じて API を適切に選択し、サポート状況やアクセシビリティの制約に対処できるかを見ています。優れた回答では、クエリコンテナに container-type が必要であること、応答するスタイルが子孫要素を対象とすることを述べ、CSS 状態クエリを汎用的なイベントシステムとして扱わないようにします。

不十分な回答は @container の構文を暗記しているだけです。優れた回答では、JavaScript が引き続き必要となる場面、プログレッシブエンハンスメントのために @supports を使用する方法、prefers-reduced-motion を尊重する方法、そして実際の sticky、snap、オーバーフロー、未対応ブラウザのケースをテストする方法を説明します。

回答前の明確化のための質問

まず、要件が視覚的なフィードバックなのかビジネスロジックなのかを尋ねます。シャドウ、色、ヒントの表示非表示は CSS に適していますが、スクロール方向の記録、ネットワークリクエストの発行、ステートマシンの駆動には JavaScript が必要です。

次に、状態の発生元を確認します。対象要素は実際に position: sticky になっているか、リストには scroll-snap-type が設定されているか、スクロールコンテナには視認可能なオーバーフローがあるかを確認します。これらの前提条件がなければ、関連するクエリは false のままになります。

最後に、ブラウザのベースラインとモーション要件を確認します。Chrome for Developers では Chrome 133 における scroll-state コンテナクエリについてドキュメント化されています。本番環境の設計では、この機能に依存しないスタイルから始め、拡張機能を supports でラップし、モーション低減を尊重する必要があります。

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

次のように答えます:

「スタイル状態とビジネスイベントを分離します。sticky、snap、スクロール可能性はブラウザの状態であるため、関連する要素に container-type: scroll-state を設定し、@container と scroll-state を使用してその子孫要素にスタイルを適用します。アナリティクス、正確な進捗、コンポーネントをまたぐビジネスアクションには JavaScript を維持します。デフォルトの CSS を利用可能にした上で、@supports 配下に機能を追加し、モーション低減ユーザー向けには不要なモーションを無効化し、オーバーフロー、snap、キーボード入力、未対応ブラウザをテストします。」

ステップバイステップの詳細な回答

scroll-state クエリコンテナを確立する

状態をクエリする対象の要素を scroll-state コンテナとして宣言します。複数のコンテナが一致する可能性がある場合は名前を付けます:

css
.sticky-heading {
  position: sticky;
  top: 0;
  container-type: scroll-state;
  container-name: heading;
}

応答するルールは、container-type を保持する要素自身ではなく、子孫要素をターゲットにする必要があります。Chrome のガイドラインでは、このプロキシ層の関係が明示的に示されています。

状態に応じて記述子を選択する

stuck は sticky 要素が端に固定されているかを問い合わせ、snapped は要素がスナップコンテナにスナップされようとしているかを問い合わせ、scrollable はある方向にオーバーフローが残っているかを問い合わせ、scrolled は直近のスクロール方向について問い合わせます。これらは異なる疑問に答えるものであり、互換性はありません。

たとえば、ナビゲーションが固定されている(stuck)間にシャドウを追加します:

css
@container heading scroll-state(stuck: top) {
  .nav {
    box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
  }
}

スタイルのターゲットをコンテナの下に配置する

container-type を保持する要素の背景を直接変更すると、分離ルールに違反しやすくなります。応答ターゲットとして内部ラッパーを保持します。これによりコンポーネントの再利用性が保たれ、状態遷移のテストが容易になります。

発見可能性のために scrollable を使用する

横スクロールのカードリストの場合、インライン方向の末尾に向かってまだスクロールできる間にグラデーションヒントを表示します:

css
.carousel {
  container-type: scroll-state;
  container-name: cards;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

@container cards scroll-state(scrollable: inline-end) {
  .next-hint {
    opacity: 1;
  }
}

ヒントは補助的なものです。「コンテンツがまだあること」を色だけで伝えないようにしてください。キーボード操作によるスクロール、視認可能なフォーカス、テキストラベルを維持します。

現在のカードのフィードバックに snapped を使用する

snapped は、スナップ祖先と有効なスナップ軸が存在する場合にのみ意味を持ちます。現在のカードをハイライトすることで視覚的フィードバックを向上できますが、これはビジネス上の選択確定シグナルとしては信頼できません。実際の選択には、アクセシブルなコントロールと JavaScript の状態が依然として必要です。

方向フィードバックに scrolled を使用する

上部ツールバーは直近のスクロール方向に応答できますが、方向を伴う動きは認知負荷を増大させる可能性があります。デフォルトでアクセシブルに保ち、メリットが明確な場合にのみ scrolled: block-start または block-end を使用し、トランジションを追加します。

プログレッシブエンハンスメントとフォールバック

ベースとなるスタイルは、scroll-state がサポートされていない場合でも利用可能である必要があります:

css
.nav {
  box-shadow: none;
}

@supports (container-type: scroll-state) {
  .sticky-heading {
    container-type: scroll-state;
  }

  @container scroll-state(stuck: top) {
    .nav {
      box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
    }
  }
}

古いブラウザで同様の合図を受け取る必要がある場合は、IntersectionObserver または ResizeObserver による小規模なフォールバックを使用します。フォールバック内で完全なスクロールイベントループを再作成しないでください。

モーションと検証

Chrome では、必須ではないモーションを prefers-reduced-motion: no-preference の配下に配置することを推奨しています。上端と下端、オーバーフローなし、キーボード操作、タッチスクロール、スナップの中間状態、ズーム、未対応ブラウザをテストします。状態の変化によってフォーカスやコンテンツが決して隠れないことを確認してください。

高品質な回答サンプル

「まず、これが視覚的フィードバックなのかビジネスイベントなのかを確認します。sticky シャドウ、snap ハイライト、スクロールヒントの場合、sticky 要素またはスナップ要素に container-type: scroll-state を設定し、stuck、snapped、scrollable クエリを使用して子孫要素を応答させます。アナリティクス、進捗計算、選択の送信は JavaScript に残します。実用的なデフォルト CSS を提供し、@supports 配下に機能強化を追加し、静的なフォールバックを維持し、要求された場合はモーションを減らします。実際のオーバーフロー、snap、キーボード入力、古いブラウザでテストします。」

よくある間違い

scroll-state をスクロールイベントとして扱う

失敗パターン:クエリを使用してアナリティクス、リクエスト、またはビジネス送信を発火させる。失敗する理由:CSS はスタイルの条件を表現するものであり、イベントのペイロードや信頼性の高いビジネスタイミングを表現するものではないため。修正方法:スタイリングは CSS に留め、ビジネスアクションはセマンティックなコントロールと JavaScript に委ねます。

クエリコンテナを忘れる

失敗パターン:container-type: scroll-state なしで @container scroll-state(stuck: top) を記述する。失敗する理由:ブラウザが評価するための状態ソースを持たないため。修正方法:sticky、snap、またはオーバーフローの前提条件を確認し、名前付きコンテナを宣言します。

コンテナ自身にスタイルを適用する

失敗パターン:1 つの要素をコンテナと状態スタイルのターゲットの両方にしてしまう。失敗する理由:クエリは子孫をターゲットとするため、構造が脆弱になります。修正方法:内部ラッパーを追加し、そこに状態フィードバックを適用します。

アクセシブルなフィードバックを色のみで置き換える

失敗パターン:現在のスナップカードや続きのコンテンツを示すために色のみを変更する。失敗する理由:色だけでは状態を確実に伝えることができず、ロービジョンのユーザーを排除する可能性があるため。修正方法:テキスト、フォーカス、コントロール、または操作可能なスクロールヒントを提供します。

サポート状況とモーションの境界を無視する

失敗パターン:すべてのブラウザがこの機能をサポートしていると想定し、スクロール中に動きを強制する。失敗する理由:古いブラウザで動作しなくなり、モーションが不快感を引き起こす可能性があるため。修正方法:@supports を使用し、デフォルトを利用可能に保ち、reduced-motion を尊重します。

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

正確なスクロールパーセンテージが必要な場合はどうしますか?

scroll-state は離散的な状態を公開するものであり、連続的なパーセンテージは公開しません。JavaScript を使用してスクロール範囲を読み取り、更新をスロットル処理して、読み取り、計算、書き込みを分離することで、各スクロールイベントが同期的にレイアウトをトリガーしないようにします。

sticky クエリが常に false になる場合はどうしますか?

sticky 要素の包含ブロック、top または inset、祖先のオーバーフロー、および要素が実際に sticky 範囲に入っているかどうかを確認します。その後で、container-type と子孫セレクタを検査します。

snap ハイライトがちらつく場合はどうしますか?

スナップ軸、scroll-snap-align、およびターゲットの寸法を確認します。レイアウトを頻繁に変更するプロパティでのトランジションは避けてください。フォーカスと選択状態は明示的に保ちます。ハイライトは視覚的な状態にすぎません。

古いブラウザでも同じように見せる必要がある場合はどうしますか?

まず実用的な静的スタイルを定義し、次に主要な視覚的手がかりに対して小さな IntersectionObserver または ResizeObserver フォールバックを追加します。複雑なスクロールリスナーシステムをコピーしてはいけません。エンハンスメントのコストが得られるメリットを上回る場合は、静的なシャドウや常に表示されるヒントを受け入れます。

ユーザーがモーションの低減を好む場合はどうしますか?

強制的な平行移動や拡大縮小を行わずに情報階層を維持します。シャドウ、ボーダー、または静的な色のみを変更し、prefers-reduced-motion でトランジションやアニメーションを上書きできるようにします。

公開情報ソース

関連する質問