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

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

Robert Flack
Robert Flack
Majid Valipour
Majid Valipour

CSS スクロール スナップ機能を使用すると、ウェブ デベロッパーはスクロール スナップ位置を宣言することで、スクロール エクスペリエンスを適切に制御できます。ページ分割された記事や画像カルーセルは、この例としてよく使われます。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 を使用している場合、ブラウザはオペレーションのスクロール位置を計算し、適切なスナップ ロジックを適用して最終的なスナップ位置を特定します。そのため、ユーザー スクリプトでスナップの計算を手動で行う必要はありません。

スムーズ スクロール

スムーズ スクロールは、プログラムによるスクロール オペレーションの動作を制御し、スクロール スナップは、その宛先を決定します。スクロールの直交する側面を制御するため、一緒に使用して相互に補完できます。

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

オーバースクロール動作 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 イベントに取り組みます。