CSS Scroll Snap を使用したスクロールの適切な制御

スクロールのスナップ位置を宣言して、適切に制御されたスクロール エクスペリエンスを実現します。

Robert Flack
Robert Flack
Majid Valipour
Majid Valipour

CSS スクロール スナップ機能を使用すると、ウェブ デベロッパーはスクロール スナップ位置を宣言することで、制御されたスクロール エクスペリエンスを作成できます。よく使用される例としては、ページ分けされた記事と画像カルーセルの 2 つがあります。CSS スクロール スナップは、これらの一般的な UX パターンを構築するための使いやすく一貫性のある API を提供します。

背景

スクロール スナップを使用する理由

スクロールは、ウェブ上のコンテンツを操作するのによく使われる、自然な方法です。画面に一度に表示できる情報量を超える情報にアクセスするためのプラットフォームのネイティブな手段であり、画面領域が限られているモバイル プラットフォームでは特に重要です。そのため、深い階層ではなく、スクロール可能なフラットリストにコンテンツを整理するウェブ作成者がますます増えています。

スクロールの主な欠点は、精度に欠けるという点です。スクロールが段落や文に揃えられることはめったにありません。これは、ページまたは画像の中間でスクロールが終了し、部分的に表示されたままになるときに、意味のある境界があるページ分けされたコンテンツや項目別のコンテンツで特に顕著です。こうしたユースケースでは、スクロール エクスペリエンスを適切に制御することでメリットが得られます。

この問題に対処するため、ウェブ デベロッパーは長い間 JavaScript ベースのソリューションを使用してスクロールを制御してきました。ただし、JavaScript ベースのソリューションでは、スクロールのカスタマイズ プリミティブがないため、または合成スクロールにアクセスできないため、完全な忠実度ソリューションを提供できません。CSS スクロール スナップは、ブラウザ間で一貫して動作する、高速で忠実度が高く、使いやすいソリューションです。

CSS スクロール スナップを使用すると、ウェブ作成者は各スクロール コンテナに、スクロール オペレーションを終了する境界をマークできます。ブラウザは、スクロール オペレーションの詳細、スクロール コンテナのレイアウトと可視性、スナップ位置の詳細に応じて、最も適切な終了位置を選択し、スムーズにアニメーション化します。前の例に戻ると、ユーザーがカルーセルのスクロールを終了すると、表示されている画像が所定の位置にスナップされます。JavaScript によるスクロールの調整は必要ありません。

画像カルーセルで CSS スクロール スナップを使用する例。
画像カルーセルで CSS スクロール スナップを使用する例。 ここでスクロールのスナップにより、スクロールの終了時に、画像の水平方向の中央がスクロール コンテナの水平方向の中央に揃えられます。

CSS のスクロール スナップ

スクロールのスナップとは、スクロール操作が終了したら、スクロール コンテナのスクロール オフセットを適切なスナップ位置に調整することです。

スクロール コンテナは、scroll-snap-type プロパティを使用してスクロール スナップに対応できます。これにより、このスクロール コンテナを子孫によって生成されるスナップ位置にスナップするようブラウザに伝えることができます。scroll-snap-type は、スクロールが行われる軸(xyboth)と、スナップの厳密度(mandatoryproximity)を決定します。詳しくは後で説明します。

スナップ位置は、要素に希望する配置を宣言することで生成できます。この位置は、指定された軸に対して指定されたように、最も近い祖先スクロール コンテナと要素が配置されるスクロール オフセットです。各軸で、startendcenter のいずれかの配置を使用できます。

start の配置は、スクロール コンテナのスナップポートの開始端を要素のスナップ領域の開始端と揃える必要があることを意味します。同様に、endcenter の配置は、スクロール コンテナのスナップポートの終端または中央を、要素のスナップ領域の終端または中央と揃える必要があることを意味します。

水平スクロール軸上のさまざまな配置の例。

次の例は、これらのコンセプトの使用方法を示しています。

スクロール スナップの一般的なユースケースは、画像カルーセルです。たとえば、スクロール時に各画像にスナップする横長の画像カルーセルを作成するには、横軸に必須の scroll-snap-type が含まれるようにスクロール コンテナを指定できます。スナップによってカルーセル内で画像が中央に配置されるように、各画像を scroll-snap-align: center に設定します。

#gallery {
  scroll-snap-type: x mandatory;
  overflow-x: scroll;
  display: flex;
}

#gallery img {
   scroll-snap-align: center;
}
<div id="gallery">
  <img src="cat.jpg">
  <img src="dog.jpg">
  <img src="another_cute_animal.jpg">
</div>

スナップ位置は要素に関連付けられているため、スナップ アルゴリズムは、要素とスクロール コンテナのサイズに基づいて、スナップするタイミングと方法をスマートに判断できます。たとえば、1 つの画像がカルーセルよりも大きい場合について考えてみましょう。単純なスナップ アルゴリズムでは、ユーザーが移動して画像全体が表示されない場合があります。しかし、仕様では、このケースを検出し、ユーザーが画像の端でしかスナップせずに自由にスクロールできるように実装する必要があります。

デモを見る | ソース

例: ジャーニーの製品ページ

スクロール スナップが役立つ一般的なケースとして、縦方向にスクロールする複数の論理セクションがあるページ(一般的な商品ページなど)があります。scroll-snap-type: y proximity; は、このようなケースに適しています。ユーザーが特定のセクションの中央までスクロールしても邪魔にならないようにし、十分に近づいてスクロールしたときに、新しいセクションに注意を向けるようにスナップします。

手順は次のとおりです。

article {
  scroll-snap-type: y proximity;
  /* Reserve space for header plus some extra space for sneak peeking. */
  scroll-padding-top: 15vh;
  overflow-y: scroll;
}
section {
  /* Snap align start. */
  scroll-snap-align: start;
}
header {
  position: fixed;
  height: 10vh;
}
<article>
  <header> Header </header>
  <section> Section One </section>
  <section> Section Two </section>
  <section> Section Three </section>
</article>

スクロール パディングとスクロール マージン

商品ページの最上部には固定位置の見出しがあります。また、スクロール コンテナがスナップされたときに上部セクションの一部を表示し、上部のコンテンツに関するデザインのヒントをユーザーに提供することも求められました。

scroll-padding プロパティは、スクロール コンテナ(スナップポート)の有効な表示可能領域の調整に使用できる新しい CSS プロパティです。スクロール スナップの配置を計算するときに使用されます。このプロパティは、スクロール コンテナのパディング ボックスに対するインセットを定義します。この例では、15vh の追加インセットが上部に追加され、ブラウザはスクロール コンテナの上端の下にある低い位置 15vh をスクロール スナップ用の垂直開始端と見なします。スナップすると、スナップ ターゲット要素の開始エッジがこの新しい位置にフラッシュされ、上部にスペースが残ります。

scroll-margin プロパティは、スナップ ターゲットの有効なボックスを調整するために使用される外側の量を定義します。これは、スナップ スクロール コンテナで scroll-padding が機能する方法と同様です。

これらの 2 つのプロパティには「snap」という単語が含まれていないことに気付いたかもしれません。これは、スクロール スナップだけでなく、関連するすべてのスクロール オペレーションでボックスを実際に変更するため、意図された動作です。たとえば、Chrome では、PageDown や PageUp などのページング スクロール オペレーションのページサイズの計算や、Element.scrollIntoView() オペレーションのスクロール量の計算で、これらの値が考慮されます。

デモを見る | ソース

他のスクロール API との連携

DOM スクロール API

スクロールのスナップは、スクリプトによって開始された操作を含むすべてのスクロール操作の後に行われます。Element.scrollTo などの API を使用している場合、ブラウザはオペレーションの目的のスクロール位置を計算し、適切なスナップ ロジックを適用して、最終的なスナップ位置を見つけます。したがって、ユーザー スクリプトでスナップ用に手動で計算を行う必要はありません。

スムーズ スクロール

スムーズ スクロールは、スクロール スナップが移動先を決定する間、プログラムによるスクロール オペレーションの動作を制御します。これらはスクロールの直交部分を制御するため、一緒に使用し、互いに補完できます。

オーバースクロールの動作

Overscroll behavior API は、複数の要素間でスクロールを連結する方法を指定し、スクロール スナップの影響を受けません。

注意事項とベスト プラクティス

ターゲット要素の配置が離れている場合は、強制スナップを使用しないでください。その結果、スナップ位置の間にあるコンテンツにアクセスできなくなる可能性があります。

多くの場合、特徴検出を行うことなく、スクロール スナップ機能を追加できます。必要に応じて、@supports または CSS.supports を使用して、CSS のスクロール スナップのサポートを検出します。 非推奨の仕様にも存在する scroll-snap-type は使用しないでください。

CSS での特徴検出

@supports (scroll-snap-align: start) {
  article {
    scroll-snap-type: y proximity;
    scroll-padding-top: 15vh;
    overflow-y: scroll;
  }
}

JavaScript での特徴検出

if (CSS.supports('scroll-snap-align: start')) {
  // use css scroll snap
} else {
  // use fallback
}

Element.scrollTo などのプログラムによるスクロール API が、常にリクエストされたスクロール オフセットで終了するとは想定しないでください。スクロールのスナップにより、プログラムによるスクロールが完了した後で、スクロール オフセットが調整される場合があります。スクロールが他の理由で中断された可能性があるため、スクロール スナップ前でもこれは良い前提ではありませんでしたが、スクロール スナップの場合は特にそうです。

今後の作業

スクロール エクスペリエンスは、Chrome チームによる最近の調査の焦点でした。アンケート結果から、プラグイン ライブラリと CSS のギャップを縮小するために追加の作業が必要な領域がいくつか特定されました。今後の作業は scroll-snap に重点が置かれます。これには以下が含まれます。

  1. ブラウザ間での API の可用性と互換性。
  2. scroll-start などの新しい CSS API の開発に取り組んでいます。
  3. snapChanged() などの新しい JS イベントの開発。