横スクロールのスライダーやセクション単位でピタッと止まるページを、JavaScript なしで作りたい——そんなときに使えるのが CSS の scroll-snap-type と scroll-snap-align です。この記事では、スクロールを「いい位置」で吸着させるスクロールスナップの基本から、よく使う横スライダーの作り方、思った通りに止まらないときの確認ポイントまでを、実際に動かせるコード付きで解説します。CSS だけでカルーセルやフルスクリーンの切り替えを作りたい初心者〜中級者の方が対象です。
目次
スクロールスナップとは
スクロールスナップは、スクロールを止めたときに、要素のキリの良い位置(左端・中央など)へ自動で吸着させる仕組みです。指を離したりホイールを止めたりすると、ブラウザが一番近いスナップ位置までスルッと合わせてくれます。これにより、スライダーの途中で中途半端に止まることがなくなり、1枚ずつめくるような操作感を CSS だけで実現できます。
使うプロパティは大きく2つです。スクロールする側の親要素(コンテナ)に scroll-snap-type を指定し、吸着させたい子要素に scroll-snap-align を指定します。この2つはセットで使うのが基本で、片方だけではスナップは効きません。
基本の使い方(横スライダー)
まずは横スクロールのスライダーを作ってみます。親要素を overflow-x: auto で横スクロールできるようにし、そこへ scroll-snap-type: x mandatory を指定します。各スライドには scroll-snap-align でスナップ位置を決めます。下のコードは実際に編集して試せます。スライダーをドラッグ(またはホイール)で横に動かし、手を離すとスライドが中央でピタッと止まるのを確認してみてください。
<div class="slider">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
</div>
.slider {
display: flex;
gap: 16px;
overflow-x: auto; /* 横スクロールを有効にする */
scroll-snap-type: x mandatory; /* x方向に必ずスナップさせる */
padding-bottom: 12px;
}
.slide {
flex: 0 0 80%; /* 1枚で幅の80%を占める */
scroll-snap-align: center; /* スナップ位置はスライドの中央 */
height: 160px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #fff;
background: #4a6cf7;
border-radius: 12px;
}
ポイントは、スクロールする親に overflow とスナップ方向を、子にスナップ位置を書くことです。flex: 0 0 80% で1枚あたりの幅を固定しているので、横並びのまま画面からはみ出し、横スクロールが生まれます。
scroll-snap-type の値を理解する
scroll-snap-type は「どの方向に」「どのくらい強く」スナップさせるかを、スクロールする親要素に指定します。値は方向と強さの2つを半角スペースで区切って書きます。
| 値 | 意味 |
|---|---|
x | 横方向(水平)にスナップする |
y | 縦方向(垂直)にスナップする |
both | 縦横どちらにもスナップする |
mandatory | 必ず一番近いスナップ位置まで吸着する(途中で止まれない) |
proximity | スナップ位置の近くまで来たときだけ吸着する(離れていれば止まれる) |
mandatory はスライダーのように「必ず1枚ずつ見せたい」場面に向き、proximity は「自由にスクロールしつつ、近づいたら整えたい」場面に向きます。記事の長い文章の中にスナップを入れると mandatory では読みづらくなることがあるため、用途で使い分けましょう。
scroll-snap-align でスナップ位置を決める
scroll-snap-align は、子要素のどこをスナップ位置(コンテナの基準線)に合わせるかを指定します。先ほどの例では center を使いましたが、左端を揃えたいときは start が便利です。
| 値 | 合わせる位置 |
|---|---|
start | 要素の先頭(横なら左端、縦なら上端)を合わせる |
center | 要素の中央を合わせる |
end | 要素の末尾(横なら右端、縦なら下端)を合わせる |
none | この要素ではスナップしない |
左揃えのカルーセルにしたいときは、CSS の scroll-snap-align: center; を scroll-snap-align: start; に変えてみてください。スライドの左端がコンテナの左に揃うようになります。
セクションごとに止まる縦スクロールを作る
縦方向のフルスクリーン切り替え(1画面ずつ表示するランディングページなど)も、方向を y にするだけで作れます。スクロールするコンテナの高さを 100vh にし、各セクションも 100vh にするのがコツです。
.page {
height: 100vh; /* 画面の高さ分だけスクロール領域にする */
overflow-y: auto;
scroll-snap-type: y mandatory; /* 縦に必ずスナップ */
}
.section {
height: 100vh; /* 1セクション=1画面 */
scroll-snap-align: start; /* 上端を合わせる */
}
これで、スクロールするたびにセクションが1画面ずつ切り替わります。ページ全体(html/body)に直接スナップを掛けることもできますが、まずは専用のコンテナを1つ用意するほうが、ヘッダーやフッターと干渉しにくく扱いやすいです。
スナップ位置に余白をつける scroll-padding と scroll-margin
固定ヘッダーがあるページでは、スナップで止まった要素がヘッダーに隠れてしまうことがあります。そんなときはコンテナ側に scroll-padding を指定すると、スナップ位置をその分だけずらせます。たとえば高さ 64px の固定ヘッダーがあるなら、次のように書きます。
.page {
scroll-snap-type: y mandatory;
scroll-padding-top: 64px; /* ヘッダーの高さ分、スナップ位置を下げる */
}
似た役割で、個々の子要素側に余白を足したいときは scroll-margin を使います。scroll-padding はコンテナ、scroll-margin は子要素に指定する、と覚えておくと迷いません。
スナップが効かないときに確認すること
親要素がスクロールできる状態になっていない
スクロールスナップは「スクロールできる要素」の中でしか働きません。横スナップなら overflow-x: auto(または scroll)、縦スナップなら overflow-y: auto が親に必要です。さらに、子要素の合計サイズが親をはみ出していないとスクロール自体が発生せず、スナップも起きません。横スライダーが動かないときは、子の flex-shrink が効いて縮んでいないか(flex: 0 0 〇〇 のように縮小を止めているか)を確認しましょう。
type と align のどちらかしか書いていない
親の scroll-snap-type と子の scroll-snap-align は両方そろって初めて機能します。親にだけ書いて子に書き忘れる、というのがよくあるミスです。スナップ対象にしたい子すべてに scroll-snap-align を付いているか見直してください。
方向が内容と合っていない
横スクロールなのに scroll-snap-type: y mandatory と書いていると、当然スナップしません。スクロールする方向と x / y の指定が一致しているかを確認しましょう。縦横どちらも使う場合は both を指定します。
まとめ
CSS のスクロールスナップは、JavaScript なしでスライダーやフルスクリーン切り替えを作れる便利な機能です。要点を振り返ります。
- スクロールする親に
scroll-snap-type(方向 +mandatory/proximity)を指定する - 吸着させたい子に
scroll-snap-align(start/center/end)を指定する - 親には
overflowが必要で、両方そろって初めてスナップが働く - 固定ヘッダーには
scroll-paddingでスナップ位置を調整する
まずは横スライダーで start と center の違いを試し、慣れてきたら縦のセクション切り替えにも応用してみてください。