CSS scroll-snap 実践ガイド ―
JSなしでカルーセル風UIを作る
カルーセルやフルスクリーンスクロールに、もうJavaScriptライブラリは要りません。CSSの scroll-snap だけで、気持ちよく「吸い付く」スクロール体験を実装できます。

scroll-snap とは何か?
Webでカルーセル(スライダー)を作ろうとすると、真っ先に思い浮かぶのはJavaScriptライブラリではないでしょうか。Swiper、Slick、Flickity……。どれも優秀なツールですが、「ただ横にスクロールさせて、ぴったり止めたい」だけのために、数十KBのライブラリを読み込むのは少し大げさです。
実は、CSSだけでスクロール位置を「スナップ」させる仕組みがあります。それが scroll-snap です。親コンテナにスクロールの方向と揃え方を指示し、子要素に「ここで止まれ」というマーカーを付けるだけ。たったこれだけで、スワイプ操作がカチッと決まる気持ちよさを実現できます。
ブラウザ対応も問題ありません。2024年時点ですべてのモダンブラウザが対応しており、現場で安心して使えるプロパティです。
基本の仕組み ― 親と子の2ステップ
scroll-snap の設計はとてもシンプルです。使うプロパティは大きく2つだけ。親要素(スクロールコンテナ)に scroll-snap-type を設定し、子要素(スナップ対象)に scroll-snap-align を設定します。
親要素:scroll-snap-type
scroll-snap-type は、スクロールの方向(x または y)と、スナップの強さ(mandatory または proximity)を指定します。mandatory は「必ずスナップポイントに止まる」、proximity は「近くにスナップポイントがあれば止まる」という意味です。
.scroll-container {
overflow-x: auto;
scroll-snap-type: x mandatory;
display: flex;
gap: 16px;
}
子要素:scroll-snap-align
子要素には scroll-snap-align で「どこを基準に揃えるか」を指定します。start(左端・上端に揃える)、center(中央に揃える)、end(右端・下端に揃える)から選べます。
.scroll-item {
scroll-snap-align: start;
flex-shrink: 0;
width: 280px;
}
これだけで、横スクロール時にアイテムの左端にぴたっと止まるUIが完成します。実際に触ってみましょう。
← 横にスクロールしてみてください。各アイテムの左端でぴたっと止まります。
mandatory と proximity の使い分け
scroll-snap を使い始めると最初に迷うのが、mandatory と proximity の違いです。実務でどちらを使うべきかは、UIの用途で決まります。
.container {
scroll-snap-type: y mandatory;
/* 必ずスナップポイントに止まる */
/* ユーザーが中途半端な位置で止めようとしても引き戻される */
}
.container {
scroll-snap-type: x proximity;
/* 近くにスナップポイントがあれば吸い付く */
/* 高速スクロール時は自由にスルーできる */
}
ポイントは、mandatory はスクロールの自由を奪う度合いが強い、ということです。フルスクリーンで1ページずつ見せるようなUIでは mandatory が最適ですが、コンテンツが多い横スクロールリストに使うと「快速にスクロールしたいのに引き戻される」とユーザーがストレスを感じることがあります。
上段(mandatory)は必ずカード端にスナップ。下段(proximity)は勢いよくスクロールするとスナップせずに流れます。
実践パターン ― 現場で本当に使うレイアウト3選
パターン1:カード型カルーセル(scroll-padding 活用)
実務でよくあるのが、「左端にパディングを空けてカードをスナップさせたい」というケースです。デザインカンプでは画面の左右にマージンがあるのに、スクロールコンテナには端までカードを並べたい。そんなときに使うのが scroll-padding です。
.carousel {
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-left: 24px;
display: flex;
gap: 16px;
padding: 0 24px;
}
.carousel-card {
scroll-snap-align: start;
flex-shrink: 0;
width: 280px;
}
プロジェクトA
CSSのみで構築したカルーセルUI。左端マージンも scroll-padding で制御。
プロジェクトB
レスポンシブ対応もカード幅の変更だけで完了します。
プロジェクトC
外部ライブラリ不要。メンテナンスコストが大幅に下がります。
プロジェクトD
タッチデバイスでもネイティブの滑らかなスクロールが効きます。
プロジェクトE
画像ギャラリー、ポートフォリオ、商品一覧に最適。
左端に24pxのパディングが効いた状態でスナップしています。
パターン2:フルスクリーン縦スクロール
LPやプレゼンテーション風のサイトでよく見る「1画面ずつ縦にスクロールする」レイアウトも、scroll-snap で簡単に実現できます。以前は fullPage.js のようなライブラリが必要でしたが、今はCSSだけで十分です。
.fullpage {
height: 100vh;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
.fullpage-section {
height: 100vh;
scroll-snap-align: start;
display: flex;
align-items: center;
justify-content: center;
}
高さ300pxのコンテナ内で、セクション単位でスナップします。実際には height: 100vh で全画面にします。
パターン3:center スナップでフォーカスUI
scroll-snap-align: center を使うと、選択されたアイテムが常に中央に来るUIを作れます。画像ギャラリーや、日付ピッカー風のUIなどに最適です。
.gallery {
overflow-x: auto;
scroll-snap-type: x mandatory;
display: flex;
gap: 12px;
padding: 0 calc(50% - 100px);
/* 最初と最後のアイテムも中央に来るよう左右にパディング */
}
.gallery-item {
scroll-snap-align: center;
flex-shrink: 0;
width: 200px;
}
アイテムが必ず中央にスナップします。最初と最後のアイテムも、padding のおかげで中央に来ます。
scroll-snap-stop で「飛ばし」を防ぐ
mandatory を指定していても、勢いよくフリックすると複数のスナップポイントを飛ばしてしまうことがあります。「必ず1枚ずつ見せたい」というUIでは、子要素に scroll-snap-stop: always を追加します。
.card {
scroll-snap-align: start;
scroll-snap-stop: always;
/* 高速フリックでも必ず各カードで止まる */
}
ただし、scroll-snap-stop: always はユーザーの操作感を大きく制限するので、使う場面は慎重に選んでください。チュートリアルのステップ画面や、注意書きを必ず読ませたいオンボーディングUIなど、「飛ばされると困る」コンテンツに限定するのがおすすめです。
レスポンシブ対応とアクセシビリティ
scroll-snap はレスポンシブ対応も簡単です。カード幅をビューポート単位(vw)やパーセンテージで指定すれば、画面サイズに応じた表示ができます。PCでは3枚表示、スマホでは1枚表示、というパターンなら以下のように書きます。
.card {
scroll-snap-align: start;
flex-shrink: 0;
width: 85vw; /* モバイル:ほぼ全幅 */
}
@media (min-width: 768px) {
.card {
width: calc(33.333% - 12px); /* PC:3列 */
}
}
アクセシビリティの面では、scroll-snap はキーボード操作(Tab + 矢印キー)にも自然に対応します。ただし、スクロールコンテナに tabindex="0" と role="region"、aria-label を付けておくと、スクリーンリーダーのユーザーにも意味が伝わりやすくなります。
<div
class="carousel"
role="region"
aria-label="プロジェクト一覧"
tabindex="0"
>
<!-- カード -->
</div>
まとめ
- scroll-snap は親要素に scroll-snap-type、子要素に scroll-snap-align を指定する2ステップで使える
- mandatory は「必ず止まる」、proximity は「近ければ止まる」。迷ったら mandatory で試してスマホで確認
- scroll-padding を使えば、デザインカンプ通りのオフセット付きスナップが可能
- scroll-snap-align: center で、選択アイテムが常に中央に来るフォーカスUIが作れる
- scroll-snap-stop: always は「飛ばされたら困る」UIだけに限定して使う
- レスポンシブ対応はカード幅を vw や % で指定するだけ。メディアクエリとの相性も良い
- role="region" と aria-label を加えることで、アクセシビリティにも対応できる