CSSのスクロールスナップを利用して横スクロールする一覧リストをつくる方法

昨今のWebやアプリのUIでよく見る、横スクロールする一覧リストのつくり方です。scroll-snapを利用してCSSのみで実現できます。

最終成果物はこちら:

上のデモは次のような仕様です。

  • n個のアイテムを1画面に横並びで表示する(ここでは3個)
  • n+1個目のアイテムをチラ見せする(ここでは4個目)
  • スワイプするとアイテムが画面左端に吸い付く
    • 最後のアイテムは右端で吸い付く

以下では、このUIの作り方を解説します。

下準備:HTMLを用意してCSSをリセットする

まず、HTMLを用意してCSSをリセットします。HTMLは次のような何の変哲もないHTMLです。li は好きなだけおいてください。a の中身もお好きにどうぞ。

<ul>
  <li>
    <a href="https://www.aozora.gr.jp/">
      <div>高瀬舟</div>
      <div>高瀬舟は京都の高瀬川を上下する…</div>
    </a>
  </li>
  ...
</ul>
ul {
  margin: 0;
  padding: 0;
}
li {
  list-style-type: none;
}
a {
  display: block;
}

ステップ1:リストを横並びにする

なにはともあれ、アイテムを横並びにしないことには始まらないので、ul をflexboxにします。

ついでにこの段階で、このUIを次のステップ2で横スクロールできるように overflow-x: auto も加えておきましょう。 ::-webkit-scrollbar でスクロールバーを隠しておくのもおすすめです。

ul {
  display: flex;
  overflow-y: hidden;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  &::-webkit-scrollbar {
    display: none;
  }
}

ステップ2:チラ見せ表現をつくる

いちどにn個のアイテムが表示されるようにしつつ、n+1個目の要素をチラ見せします。

以下の例はステップ1からの差分になっていますので、それぞれのセレクターに新しいプロパティを追加してください。

ul {
  --gap: 30px;
}
li {
  padding: 0 0 0 var(--gap);
  &:last-of-type {
    padding: 0 var(--gap);
  }
}
a {
  width: calc(33.333333vw - var(--gap) - var(--gap) * 2 / 3);
}

--gap はアイテム間の隙間です。この例では、すべてのアイテムの左右に30pxの余白を設けています。ステップ3で吸いつかせるときに画面端に余白がほしいので、li に余白をつけてます。

1つのアイテムの幅は a で指定していて、calc() の式を解説すると、次のようになっています。

アイテムの幅 = 画面幅を1画面のアイテム数で分割した値 - liのpadding - (チラ見せする幅 + チラ見せするアイテムの左padding) / 1画面のアイテム数

この例ではチラ見せする幅はアイテム間の余白と揃えているので、括弧書きの部分を var(--gap) * 2 としています。

たとえば、1画面あたりのアイテム数を2にしたい場合は、

width: calc(50vw - var(--gap) - var(--gap) * 2 / 2);

なので、次のように書けます。

width: calc(50vw - var(--gap) * 2);

ステップ3:吸い付くようにする

最後にscroll-snap-typeを使ってスワイプ後にアイテムが画面端に吸い付くようにしましょう。

ul {
  scroll-snap-type: x mandatory;
}
li {
  scroll-snap-align: start;
}

完成です!

最終的なCSS(SCSS)

こんなかんじになりました。

ul {
  --gap: 30px;
  margin: 0;
  padding: 0;
  display: flex;
  overflow-y: hidden;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
  &::-webkit-scrollbar {
    display: none;
  }
}
li {
  list-style-type: none;
  scroll-snap-align: start;
  padding: 0 0 0 var(--gap);
  &:last-of-type {
    padding: 0 var(--gap);
  }
}
a {
  display: block;
  width: calc(33.333333vw - var(--gap) - var(--gap) * 2 / 3);
}

応用:複数行/複数列をまとめてスワイプできるようにする

上記の例では、1つのliを1アイテムに対応させましたが、liの中に複数のアイテムを縦に積めば、複数行まとめてのスワイプができるようになりますね。

同様に、liの中に複数の列を含めば、複数列をまとめてスワイプすることができます。

1スワイプで3×5のアイテムをスワイプする、なんてこともできるので、機会があればお試しください。