本文へスキップ

スクロールスナップ

縦スクロールで全画面セクションがピタッと止まる。scroll-snap-type: y でページめくり感を演出。

ENGLISH

Full-Page Scroll Snap

  • CSSのみ
40動きと演出 / スクロール演出(応用) の一覧 →

プレビュー

スクロール

操作枠内をスクロールすると動きます。

使いどころ

フルスクリーンの自己紹介ページ、写真ギャラリー、オンボーディングのような『区切りのはっきりした塊を1つずつ見せたい』構成に向く。セクションごとに情報が完結していて、途中で止まっても意味が通る内容と相性がよい。逆に、通常の記事やブログのような連続した文章には向かない。scroll-snap-type: y mandatory は必ずどこかのスナップ位置へ強制的に吸着させるため、セクションの途中でじっくり読みたい・行き来したいコンテンツをここに入れると、読もうとするたびに勝手に次のセクションへ引っ張られて読みにくくなる。セクションの高さがビューポートより高い場合も同様に、スクロールが完了する前に次のスナップへ吸い寄せられて中身が読めなくなることがある。

仕組み

この実装にJavaScriptは一切無く、CSSの scroll-snap-type: y mandatory(コンテナ側)と scroll-snap-align: start(各セクション側)だけで完結している。JSで位置を計算してscrollToするアプローチと違い、実際のネイティブスクロール(ホイール・トラックパッド・タッチのフリック・キーボード)をブラウザがそのまま扱い、指を離した/ホイールを止めた瞬間に最寄りのスナップ位置へ滑らかに減速して止まる物理演算もブラウザ任せになる。mandatory は『必ずどこかのスナップ点で止まる』指定で、緩めたい場合は proximity にすると、スクロール量が小さいときはスナップせず自由に止まれるようになる。-webkit-overflow-scrolling: touch はiOSでの慣性スクロールを有効にするための指定で、無いとタッチでの吸着挙動が硬くなることがある。

実装の注意

セクションの中に、それ自体がスクロールする長いリストや折りたたみパネルを置くと、親コンテナが先にスナップで次のセクションへ持っていかれてしまい、中の要素までホイールスクロールで届かないことがある。ネストしたスクロール領域を作る場合は、この構造を避けるかスナップの対象から外す必要がある。もう1つは、勢いよくスクロールしたときに1つのセクションを飛び越えて次のスナップ点まで一気に進んでしまう挙動で、途中のセクションを絶対に見せたい場合は scroll-snap-stop: always をそのセクションに指定して素通りを防ぐ(このデモには入れていない)。また .snap-root 自体がビューポートではなく高さ300pxの内側スクロール領域なので、ページ全体をスナップさせたい場合は html/body 側に指定を移し、固定ヘッダーの高さぶん scroll-padding-top で補正する必要がある。

アクセシビリティ

ネイティブのスクロールをそのまま使っているため、PageDown/PageUp・Space・矢印キーでのキーボードスクロールもブラウザの既定動作としてそのまま効く。ただし overflow-y: scroll を付けた div は既定ではフォーカス可能な要素ではないため、マウスやタッチを使わずキーボードだけで操作するユーザーのために .snap-root に tabindex="0" を付け、Tabで到達できるようにしておく必要がある(付けないと、内部にリンクなどフォーカス可能な要素が無い限りキーボードでスクロールする手段が無くなる)。mandatory による強制的な吸着とスクロールの急な減速は、前庭障害があるユーザーには不快感やめまいの原因になりうるため、@media (prefers-reduced-motion: reduce) では scroll-snap-type: none に切り替えて自由なスクロールに戻すべき(このデモには未実装)。セクション同士は見出し(h2等)で区切っておくと、スクリーンリーダー利用者は見出しジャンプでスナップに関係なく移動できる。

調整して使う

軸をx方向にすればカルーセル的な横送りにもなり(実際、上のカルーセルもscroll-snap-type: xを利用している)、scroll-snap-align を start/center/end のどれにするかでセクションの合わせ位置が変わる。mandatory と proximity の使い分けが最大の調整点で、ストーリー性を強く出したいランディングページは mandatory、通常のセクション区切りに軽く添えたい場合は proximity が扱いやすい。ドットインジケーターやセクション番号を横に添えると、今どのセクションにいるか・あと何個あるかが伝わりやすくなる(この実装には無いが、IntersectionObserverと組み合わせて追加できる)。

コード(コピーして使えます)

<div class="snap-root">
  <section class="snap-sec" style="background:#2b50c7">Section 1</section>
  <section class="snap-sec" style="background:#1a8c5b">Section 2</section>
  <section class="snap-sec" style="background:#b5341e">Section 3</section>
</div>
<div class="snap-hint">↓ スクロール</div>

タグ

同じカテゴリ「スクロール演出(応用)」のほかのパーツ

関連パーツ