コンテナースクロール状態クエリーの使用

コンテナースクロール状態クエリーは、コンテナークエリーの一種です。コンテナーのサイズに基づいて子孫要素にスタイルを選択的に適用する代わりに、スクロール状態クエリーを使用すると、コンテナーのスクロール状態に基づいて子孫要素にスタイルを選択的に適用することができます。これには、コンテナーが部分的にスクロールされているかどうか、スクロールスナップコンテナーの祖先にスナップされているかどうか、 position: sticky によって配置され、スクロールコンテナーの祖先の境界に固定されているかどうかを含めることができます。

この記事では、コンテナーのスクロール状態クエリーの使用方法について、それぞれの種類の例を交えて説明します。コンテナークエリーに関する基礎知識がある方を対象としています。コンテナークエリーについてよく分からない方は、CSS コンテナークエリーを一読してから続けてください。

コンテナーのスクロール状態クエリーの種類

scroll-state() クエリーで使用できる @container の記述子は 3 つあります。

  • scrollable: コンテナーが、ユーザーによるスクロール(例えば、スクロールバーをドラッグしたり、トラックパッドのジェスチャーを使用したり)によって、指定された方向にスクロールできるかどうかを問い合わせます。つまり、指定された方向にスクロールできるコンテンツが溢れているか、ということです。これは、スクロールコンテナーのスクロール位置に関連するスタイルを適用する場合に便利です。例えば、スクロールバーが上部に表示されているときに、ユーザーにスクロールしてコンテンツをさらに表示するよう促すヒントを表示し、ユーザーが実際にスクロールを開始したらそのヒントを非表示にすることができます。
  • snapped: コンテナーが、指定された軸に沿ってスクロールスナップコンテナーの祖先にスナップされているか、またはスナップされるかどうかを照会します。これは、要素がスクロールスナップコンテナーにスナップされている場合にスタイルを適用するのに役立ちます。例えば、スナップされた要素を何らかの方法で強調表示したり、前回は非表示だったコンテンツの一部を表示したりする場合などです。
  • stuck: position 値が sticky のコンテナーが、そのスクロールコンテナーの祖先の端に固定されているかどうかを照会します。これは、固定されたときに position: sticky 要素のスタイルを別々に指定する場合に便利です。たとえば、異なる配色やレイアウトを指定することができます。

構文概要

コンテナー要素をスクロール状態クエリーのコンテナーとして設定するには、その要素の container-type プロパティに scroll-state の値を設定します。オプションで、特定のコンテナークエリーでターゲット指定できるように、 container-name も指定することができます。

css
.container {
  container-type: scroll-state;
  container-name: my-container;
}

次に、 @container ブロックを作成して、クエリー、テストが渡された場合にコンテナーの子に適用されるルール、およびオプションで、クエリーするコンテナーのコンテナー名を指定します。コンテナー名を指定しない場合、コンテナークエリーはページ内のすべてのスクロール状態クエリーコンテナーに適用されます。

ここでは、 my-container という名前付きコンテナーのみクエリーして、コンテナーをその上端までスクロールできるかどうかを判断しています。

css
@container my-container scroll-state(scrollable: top) {
  /* CSS ルールをここに置く */
}

メモ: スクロール状態クエリーを他のコンテナークエリーから分離するために、スクロール状態記述子と値は、 scroll-state を前に付けて括弧で囲みます (scroll-state( ... ))。これらの構文は関数のように見えますが、関数ではありません。

scrollable クエリーの使用

スクロール状態の scrollable クエリーは、 scroll-state(scrollable: value) と記述され、コンテナーのスクロール可能な親要素が、ユーザーによるスクロールで指定された方向にスクロールできるかどうかを検査します。できない場合、クエリーは false を返します。

value は、スクロールの可否を検査する方向を示します。例えば、

  • top: コンテナーの上端に向かってスクロールできるかどうかを検査します。
  • inline-end: コンテナーをインライン方向の末尾に向かってスクロールできるかどうかを検査します。
  • y: コンテナーが Y 軸に沿ってどちらかの方向、あるいは両方向にスクロールできるかどうかを検査します。

検査に合格すると、@container ブロック内のルールが、一致するスクロールコンテナーの子孫に適用されます。

コンテンツでいっぱいのスクロール可能なコンテナーと、必要に応じて先頭に戻れる便利な小さなリンクがある例を見てみましょう。 scrollable クエリーを使用して、ユーザーがコンテンツをスクロールし始めたときにのみリンクを表示します。

HTML

HTML には、文書をスクロールさせるのに十分なコンテンツを含む <article> 要素があり、その前に先頭へ戻るリンクが置かれています。

html
<a class="back-to-top" href="#" aria-label="Top of page">↑</a>
<article>
  <h1>コンテナークエリーで制御する「先頭へ戻る」リンクのついたリーダー</h1>
  <section>
    <header>
      <h2>この最初の節は興味深い</h2>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </header>

    ...
  </section>

  ...
</article>

簡潔にするため、HTML の大部分を非表示にしています。

CSS

.back-to-top リンクには、 positionfixed の値が指定され、ビューポートの右下隅に配置され、 translate80px 0 の値を使用してビューポート外に移動されています。 transition の値は、 translate およびbackground-color のいずれかの値が変更されたときに、アニメーションします。

css
.back-to-top {
  width: 64px;
  height: 64px;
  color: white;
  text-align: center;
  position: fixed;
  bottom: 10px;
  right: 10px;
  translate: 80px 0;
  transition:
    0.4s translate,
    0.2s background-color;
}

この例でのスクロールコンテナーは、 <html> 要素自体であり、 container-type の値が scroll-state のスクロール状態クエリーコンテナーとして表されます。コンテナー名は厳密には必要ではありませんが、異なるクエリーをターゲットとする複数のスクロール状態クエリーコンテナーがコードベースに追加されている場合に便利です。

css
html {
  container-type: scroll-state;
  container-name: scroller;
}

次に、このクエリーの対象となるコンテナー名を設定する @container ブロックと、クエリー自体 (scrollable: top) を定義します。このクエリーは、 <html> 要素が上端に向かってスクロールできる場合、つまりコンテナーが以前に下方向にスクロールされている場合にのみ、ブロック内に含まれるルールを適用します。その場合は、.back-to-top リンクに translate: 0 0 が適用され、リンクが画面上に戻ります。

css
@container scroller scroll-state(scrollable: top) {
  .back-to-top {
    translate: 0 0;
  }
}

簡潔にするため、CSS の残りの部分は省略しています。

結果

文書を下にスクロールして、「トップへ戻る」リンクがどのように表示されるかをメモしてください。transition により、ビューポートの右側からスムーズにアニメーションして表示されます。リンクをクリックするか、手動でスクロールしてトップに戻ると、「トップへ戻る」リンクは画面の外側に移動します。

snapped クエリーの使用

スクロールスナップが実装されている場合にのみ関連しますが、スクロール状態の snapped クエリー(scroll-state(snapped: value) と記述)は、コンテナーが、指定された軸に沿ってスクロールスナップコンテナーの祖先にスナップされているか、またはスナップされるかどうかを検査します。そうでない場合、クエリーは false を返します。

この場合の value は、要素のスナップする方向を示すものです。例えば、

  • x: コンテナーが、そのスクロールスナップコンテナーの親コンテナーに対して水平方向にスナップしているかどうかを検査します。
  • inline: コンテナーが、インライン方向にスクロールスナップコンテナーの祖先にスナップしているかどうかを検査します。
  • y: コンテナーが、スクロールスナップコンテナーの親要素に対して両方向にスナップしているかどうかを検査します。

none 以外の snapped であるスクロール状態のクエリーでコンテナーを評価するには、そのコンテナーはスクロールスナップコンテナーの祖先を持つコンテナー、つまり、祖先が none 以外の scroll-snap-type 値を持つコンテナーでなければなりません。コンテナークエリーの scroll-state(snapped: none) は、スクロールコンテナーの祖先を持たないスクロール状態コンテナーと一致します。0

評価は、スクロールスナップコンテナーで scrollsnapchanging イベントが発生すると行われます。

条件を満たすと、@container ブロック内のルールが、一致するスクロールスナップターゲットコンテナーの子孫に適用されます。

この例では、子要素が垂直方向にスナップするスクロールスナップコンテナーを見て、snapped クエリーを使用して、子要素がスナップされているか、スナップしようとしている場合にのみ、その子要素のスタイルを設定します。

HTML

HTML は、スクロールスナップコンテナーとなる <main> 要素で構成されています。その中には、スナップターゲットとなる複数の <section> 要素があります。それぞれの <section> には、ラッパーの <div><h2> 見出しが含まれています。コンテナークエリーでは、コンテナーそのものではなく、コンテナーの子孫にスタイルを設定できるよう、スタイルターゲットを作成するためにラッパーが含まれています。

html
<main>
  <section>
    <div class="wrapper">
      <h2>Section 1</h2>
    </div>
  </section>

  ...
</main>

簡潔にするため、HTML のほとんどは省略しています。

CSS

overflow 値を scroll に設定し、 <main> 要素に固定値の height を設定して、垂直スクロールコンテナーにします。また、 scroll-snap-type 値を y mandatory に設定して、 <main> を、スナップターゲットが Y 軸に沿ってスナップするスクロールスナップコンテナーにします。 mandatory は、スナップターゲットが常にスナップされることを意味します。

css
main {
  overflow: scroll;
  scroll-snap-type: y mandatory;
  height: 450px;
  width: 250px;
  border: 3px solid black;
}

<section> 要素は、 none 以外の scroll-snap-align 値を設定することで、スナップターゲットとして指定されます。 center の値は、要素がコンテナーの中心点にスナップすることを意味します。

css
section {
  font-family: Arial, Helvetica, sans-serif;
  width: 150px;
  height: 150px;
  margin: 50px auto;

  scroll-snap-align: center;
}

<section> 要素をクエリーできるようにしたいと思います。具体的には、 <section> 要素がコンテナーにスナップしているかどうかを検査したいので、 container-type 値を scroll-state に設定して、それらをスクロール状態クエリーコンテナーとして指定します。また、 container-name も指定していますが、これは厳密には必要ではありませんが、後でコードが複雑になり、様々なクエリー-でターゲットとする複数のスクロール状態クエリーコンテナーがある場合に役立つちます。

css
section {
  container-type: scroll-state;
  container-name: snap-container;
}

次に、 @container ブロックを定義し、このクエリーをターゲットとするコンテナーの名前と、クエリー自体(snapped: y)を定義します。このクエリーは、 <section> 要素がコンテナーに垂直方向にスナップされている場合にのみ、ブロック内に含まれるルールを適用します。その場合は、新しい backgroundcolor<section> 要素の子である .wrapper<div> に適用して、それを強調表示します。

css
@container snap-container scroll-state(snapped: y) {
  .wrapper {
    background: purple;
    color: white;
  }
}

結果

レンダリング結果は次のとおりです。コンテナーを上下にスクロールして、コンテナーにスナップすると <section> のスタイルがどのように変化するかを確認してください。

stuck クエリーの使用

スクロール状態の stuck クエリーは、 scroll-state(stuck: value) と記述され、 position 値が sticky であるコンテナーが、そのスクロールコンテナーの祖先の端に固定されているかどうかを検査します。そうでない場合、クエリーは false を返します。

この場合、value は検査するスクロールコンテナーの端を示します。例えば、

  • top: コンテナーが、そのスクロールコンテナーの祖先の最上端に貼り付いていないかを検査します。
  • block-end: コンテナーが、そのスクロールコンテナーの祖先のブロック方向の端に貼り付いていないかを検査します。
  • none: コンテナーが、そのスクロールコンテナーの祖先の端に貼り付いていないかどうかを確認します。 none クエリーは、コンテナーに position: sticky が設定されていなくても一致することに注意してください。

クエリーが true を返す場合、 @container ブロック内のルールは、一致する position: sticky コンテナーの子孫に適用されます。

コンテンツがオーバーフローするスクロール可能なコンテナーがあり、その見出しが position: sticky に設定されており、その位置までスクロールするとコンテナーの上端に固定される例を見てみましょう。 stuck スクロール状態クエリーを使用して、見出しが上端に固定されたときに、そのスタイルを変更します。

HTML

HTML には、文書をスクロールさせるのに十分なコンテンツを含む <article> 要素があります。これは、複数の <section> 要素を使用して構成されており、各要素には、ネストされたコンテンツを含む <header> が含まれています。

html
<article>
  <h1>スクロール状態コンテナークエリーのある張り付くリーダー</h1>
  <section>
    <header>
      <h2>この最初の節は興味深い</h2>

      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </header>

    ...
  </section>

  <section>
    <header>
      <h2>これはそれほどではありません</h2>

      <p>Confecta res esset.</p>
    </header>

    ...
  </section>

  ...
</article>

簡潔にするため、HTML のほとんどを省略しています。

CSS

それぞれの <header> には、 position 値が stickytop 値が 0 が設定されており、スクロールコンテナーの上端に固定されています。 <header> 要素がコンテナーの上端に固定されているかどうかをテストするために、これらの要素は、 container-type 値が scroll-state のスクロール状態クエリーコンテナーとして指定されています。 container-name は必須ではありませんが、異なるクエリーをターゲットとする複数のスクロール状態クエリーコンテナーがコードベースに追加されている場合に便利です。

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

また、 <header> 要素内の <h2> および <p> 要素に基本的なスタイル設定を行い、 transition 値を指定して background 値が変更されたときにスムーズにアニメーションするようにします。

css
h2,
header p {
  margin: 0;
  transition: 0.4s background;
}

h2 {
  padding: 20px 5px;
  margin-bottom: 10px;
}

header p {
  font-style: italic;
  padding: 10px 5px;
}

次に、 @container ブロックを定義し、このクエリーの対象となるコンテナーの名前と、クエリー自体(stuck: top)を設定します。このクエリーは、<header> 要素がスクロールコンテナーの上端に固定されている場合にのみ、ブロック内に含まれるルールを適用します。その場合は、別の背景と box-shadow が、中の <h2> および <p> に適用されます。

css
@container sticky-heading scroll-state(stuck: top) {
  h2,
  p {
    background: #ccc;
    box-shadow: 0 5px 2px #0007;
  }
}

簡潔にするため、残りの CSS は省略しています。

結果

文書を下にスクロールしてから上にスクロールして、<h2> および <p> 要素がコンテナーの上端に固定されると、新しい配色に切り替わることを確認してください。

関連情報