CSSでアプリのようにピタッとスナップするスクロールを作成

出典元 CSS Scroll Snap - Ahmad Shadeed

アプリなどで横スクロールしてコンテンツにスナップするといった動作をよく見かけると思いますが、CSSで横スクロールのUIを作成しようとすると、専用のjavascriptやライブラリを使用する必要があったりと実装が大変です。
しかし、CSSスクロールスナップは横スクロールしてスナップするといった動作を行うことができます。

この記事では、CSSスクロールスナップ(scroll-snap)の基本について説明します。

scroll-snapを使用する理由

スマホやタブレットの普及で、タッチでスワイプできる画面を設計、構築する必要があるかと思います。
たとえば、ギャラリー風のデザインを考えてみましょう。ユーザーは、階層構造ではなく、左または右に簡単にスワイプして、より多くの画像を表示できます。

CSSの仕様によると、CSSスクロールスナップを導入することでユーザーエクスペリエンスが向上し、スクロールの実装が容易になり、ユーザーと開発者、双方にメリットがあるということです。

スクロールコンテナーの基本

スクロールコンテナーを作成するために必要な基本的なものは次のとおりです。

  • overflowをvisible以外の値で使用
  • 項目を隣同士(インライン)に表示す

例を見てみましょう。

<div class="section">
  <div class="section__item">Item 1</div>
  <div class="section__item">Item 2</div>
  <div class="section__item">Item 3</div>
  <div class="section__item">Item 4</div>
  <div class="section__item">Item 5</div>
</div>
.section {
  white-space: nowrap;
  overflow-x: auto;
}

以前まではwhite-space: nowrapを使用することで要素を横並びで表示するようにしていました。
近年では、この方法の代わりにFlexboxを使用することで表現可能です。

.section {
  display: flex;
  overflow-x: auto;
}

これは、スクロールコンテナーを作成するための基本的な構成です。
しかし、これだけではスクロールコンテナーの機能としては不十分です。

従来の問題

従来の問題は、スワイプの仕組みと比較して、優れたUXが提供されないことです。タッチスクリーンでのスワイプジェスチャの主な利点は、1本の指で水平または垂直にスクロールできることです。
以前のソリューションでは、ただスクロールするだけです。文字通り、各アイテムをそれぞれの場所に移動する必要があります。これはスワイプではなく、ユーザーにとって非常にストレスを与える結果になってしまします。
CSSスクロールスナップを使用すると、ユーザーが水平方向または垂直方向にスクロールしやすくなるスナップポイントを定義するだけで、この問題を解決できます。

CSSスクロールスナップの使用方法を見てみましょう。

CSSスクロールスナップの紹介

コンテナーでスクロールスナップを使用するには、その子アイテムをinlineで表示する必要があります。これは、上記で説明した方法の1つで実行できます。 CSSフレックスボックスを使用します。

<div class="section">
  <div class="section__item">Item 1</div>
  <div class="section__item">Item 2</div>
  <div class="section__item">Item 3</div>
  <div class="section__item">Item 4</div>
  <div class="section__item">Item 5</div>
</div>
.section {
  display: flex;
  overflow-x: auto;
}

さらに、スクロールスナップを機能させるために2つのプロパティを追加します。
まず、スクロールコンテナーにscroll-snap-typeを追加します。この例では、これは.section要素です。
次に、子アイテム(.section__item)にscroll-snap-alignを追加します。

.section {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.section__item {
 scroll-snap-align: start;
}

x mandatorystartの値についてはじめて見たと思われるかもしれませんが、この部分が今回の記事のメインになる部分です。

これらのプロパティにより、スクロールコンテナーの開始位置にスナップすることが可能になって、スクロールがより自然になりました。
それでは、スクロールスナップのプロパティについて詳しく見ていきましょう。

scroll-snap-type

CSSの仕様 によると、scroll-snap-typeは、ある要素がスクロールスナップコンテナーであるかどうか、どの程度厳密にスナップするか、どの軸を考慮するかを指定するものです。

それを解析してみましょう。

スクロールスナップコンテナーの軸について

スクロールスナップコンテナーの軸は、スクロールの方向を表します。水平または垂直にできます。
xの値は水平方向のスクロールを、yの値は垂直方向のスクロールを表します。

/* Horizontal */
.section {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x;
}
/* Vertical */
.section {
  height: 250px;
  overflow-y: auto;
  scroll-snap-type: y;
}

スクロールスナップコンテナーの精密さ

スクロールスナップの方向だけでなく、その精密さも定義できます。
これは、scroll-snap-type値にmandatory | proximityのいずれかの値を使用することで可能です。

mandatoryは、ブラウザが各スクロールポイントにスナップする必要があることを意味します。scroll-snap-alignプロパティの値がstartであると仮定してみましょう。つまり、スクロールはスクロールコンテナーの開始点にスナップする必要があります。

下図では、ユーザーが右方向にスクロールするたび、ブラウザはコンテナーの開始点にアイテムをスナップさせます。

.section {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
.section__item {
 scroll-snap-align: start;
}

下のデモで右方向にスクロールしてみてください。スクロールバーを右に移動させるか、スマートフォンやタブレットの場合はタッチ操作で行ってください。各アイテムがコンテナーの始まりにスナップする様子が感じられるはずです。

しかし、値がproximityの場合は、ブラウザが作業を行います。定義された点(この例ではstart)にスナップするかもしれません。proximityはデフォルトの値ですが、わかりやすくするために追加しておきます。

.section {
  display: flex;
  overflow-x: auto;
  /* proximityはデフォルト値です。 */
  scroll-snap-type: x proximity;
}

スクロールスナップの方向

スクロールコンテナーの子アイテムには、スナップできるアライメントポイントが必要です。
startcenterendのいずれかを使用します。

スクロールコンテナーに磁石があり、スナップポイントを制御するのに役立つと想像してみてください。
scroll-snap-typeが垂直の場合、スナップの配置は垂直になります。次の図を参照してください。

これをより明確にするため、 startcenter、およびendの以下のアニメーションを参照してください。

スクロールコンテナーの start

子アイテムは、水平スクロールコンテナーの先頭にスナップします。

スクロールコンテナーの center

子アイテムは、スクロールコンテナーの中央にスナップします。

スクロールコンテナーの end

子アイテムは、スクロールコンテナーの最後にスナップします。

scroll-snap-stopを使う

ユーザーがあまりに速くスクロールするなど、スクロール中にユーザーが誤って重要な項目をスキップしてしまうのを防ぐ方法が必要な場合もあります。

.section__item {
  scroll-snap-align: start;
  scroll-snap-stop: normal;
}

スクロールのスピードが速すぎる(勢いよくスクロールする)と、3つも4つも項目が飛ばされることもあります。

scroll-snap-stop のデフォルト値はnormalです。スクロールを強制的にすべての可能なポイントにスナップするには、alwaysを使用する必要があります。 scroll-snap-stop: alwaysを使用すると、ブラウザは各スナップポイントで停止します。

.section__item {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

そうすれば、ユーザーは1つずつスナップポイントをスクロールしていくことができ、重要な項目をスキップすることを避けることができます。
各ストップポイントにストップサインがあることを想像してください。

デモで下のスクロールを試して、オプションを切り替えてみてください。

スクロールスナップのpadding

scroll-padding短縮形プロパティは、paddingプロパティの動作と同様に、すべての側面にスクロールパディングを設定します。
下図では、スクロールコンテナーの左側に50pxのパディングが設定されています。その結果、子要素は左端から50pxずれた位置にスナップします。

.section {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding: 0 0 0 50px;
}

同じことが垂直スクロールでも機能します。以下の例を参照してください。

.section {
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scroll-padding: 50px 0 0 0;
}

スクロールスナップのmargin

scroll-marginショートハンドプロパティは、スクロールコンテナーの子アイテム間の間隔を設定します。要素にマージンが追加されると、マージンに応じてスクロールがスナップします。下図を参照してください。

.item-2scroll-margin-left: 20pxとなっています。その結果、スクロールコンテナーはそのアイテムの手前20pxにスナップします。ユーザーが再び右にスクロールしたとき、.item-3はスクロールコンテナーの開始位置にスナップすることに注意してください。つまり、マージンを持つ要素のみが影響を受けることになります。

CSSスクロールスナップの使用例

画像リスト

CSSスクロールスナップの優れた使用例として、画像のリストが挙げられます。スクロールスナップを使用することで、より良いスクロール体験を提供します。

.images-list {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x;
  gap: 1rem;
  -webkit-overflow-scrolling: touch; /* Important for iOS devices */
}
.images-list img {
 scroll-snap-align: start;
}

scroll-snap-typeの値としてxを使用したことに注意してください。スナップの厳密さは、デフォルトではproximityになります。

友達リスト

スクロールスナップのもう1つの優れた使用例として、友達のリストがあります。以下の例は、Facebookから引用したものです(実例)。

.list {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 1rem;
  scroll-padding: 48px;
  padding-bottom: 32px;
  -webkit-overflow-scrolling: touch;
}
.list-item {
 scroll-snap-align: start;
}

スクロールするコンテナーにはpadding-bottomがあることに注意してください。32pxです。この目的は、ボックスシャドウが期待どおりに表示されるように余分なスペースを提供することです。

アバターリスト

今回の使用例では、子アイテムのscroll-snap-alignの値としてcenterを使用しています。

.list {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.list-item {
 scroll-snap-align: center;
}

これはアバターのリストで、アバターがスクロールするコンテナーの中央にあることが重要な場合に便利です。

全画面を覆うような要素

スクロールスナップの使用は、縦スクロールの場合にも有効です。この例として、フルハイトのセクションがあります。

<main>
  <section class="section section-1"></section>
  <section class="section section-2"></section>
  <section class="section section-3"></section>
  <section class="section section-4"></section>
  <section class="section section-5"></section>
</main>
main {
  height: 100vh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  -webkit-overflow-scrolling: touch;
}
.section {
  height: 100vh;
  scroll-snap-align: start;
}

ブロックおよびインラインの値

特筆すべきは、scroll-snap-typeinlineblockという論理値を使用できることです。以下の例を参照してください。

main {
  scroll-snap-type: inline mandatory;
}

この例では、英語のような横書きモードでは、inlineが横方向の寸法を表します。日本語のような言語では、inlineは縦方向の寸法を表すことになります。
CSSの論理プロパティについてもっと知りたい方は、Adrian Roselliの記事 をご覧ください。

アクセシビリティ

CSSスクロールスナップを使用する際は、アクセシビリティを確保すること。ここでは、ユーザーがコンテンツを自由にスクロールして読むことを妨げる、スクロールスナップの悪い使い方を紹介します。

.wrapper {
 scroll-snap-type: y mandatory;
}
h2 {
 scroll-snap-align: start;
}

デモのような実装は絶対にしないでください。

まとめ

以上がscroll-snap機能についてになります。今までスナップするスクロールを実装する場合はjsを使用していましたが、cssのみで実装可能となると簡単になりますね。

オススメの書籍

第7回 pythonでNQueen(エイトクイーン)バックトラック(3)

第7回 pythonでNQueen(エイトクイーン)バックトラック(3)

(7)【kill】シェルスクリプトコマンド活用紹介

(7)【kill】シェルスクリプトコマンド活用紹介