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

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

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

背景

スクロール スナップのケース

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

スクロールの主な欠点は、精度が低いことです。スクロールが段落や文に揃うことはめったにありません。これは、意味のある境界を持つページ分割されたコンテンツや項目化されたコンテンツで、スクロールがページや画像の中央で終了し、部分的に表示されたままになる場合に特に顕著です。これらのユースケースでは、適切に制御されたスクロール エクスペリエンスが役立ちます。

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

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

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

CSS スクロール スナップ

スクロール スナップとは、スクロール操作が完了したときに、スクロール コンテナのスクロール オフセットを、優先されるスナップ位置に調整する動作です。

スクロール コンテナは、scroll-snap-type プロパティを使用してスクロール スナップにオプトインできます。これにより、このスクロール コンテナを子孫によって生成されたスナップ位置にスナップすることを検討する必要があることがブラウザに通知されます。scroll-snap-type は、スクロールが発生する軸(x、y、both)と、スナップの厳格さ(mandatory、proximity)を決定します。これらについては後ほど詳しく説明します。

スナップ位置は、要素に目的の配置を宣言することで生成できます。この位置は、最も近い祖先スクロール コンテナと要素が、指定された軸に沿って配置されるスクロール オフセットです。各軸で可能な配置は、start、end、center です。

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

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

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

スクロール スナップの一般的なユースケースは、画像カルーセルです。たとえば、スクロールすると各画像にスナップする横向きの画像カルーセルを作成するには、スクロール コンテナに水平軸で必須の 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 つの画像がカルーセルよりも大きい場合を考えてみましょう。単純なスナップ アルゴリズムでは、ユーザーがパンして画像全体を表示できなくなる可能性があります。しかし、仕様では、実装でこのケースを検出し、ユーザーがその画像内を自由にスクロールして、端でのみスナップできるようにする必要があります。

例: 段階的な商品ページ

スクロール スナップが役立つもう 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 Scrolling 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 イベントを処理する。