🚦

今どきの JavaScript と CSS でカルーセルを作ってみた

2021/01/30に公開

はじめに

JavaScript や CSS は日々進化しています!
今まで jQuery やライブラリを駆使して実装していた機能も, 実は Pure JavaScript/CSS で実現できるようになっていたりします.

そんな中から, 今回は CSS の scroll-snap と JavaScript の scrollIntoView() を使ったカルーセルの実装方法を紹介したいと思います.
外部ライブラリを一切使わず, 標準の JavaScript と CSS のみでサンプル作ったのでぜひ参考にしてみてください.

サンプルプログラム

今回作成したサンプルプログラムです.

横スクロールするとピタッと画像がキレイに見える状態で止まります.
また, 下にある < > ボタンを押して左右にスクロールすることもできます.

https://runstant.com/phi/projects/15ca1577

コード

html では, .carousel 要素の中に画像を並べています.
その下に prev/next ボタンも配置しています.

index.html
<body>
  <div class='carousel'>
    <img src='https://www.evernote.com/l/AOJvwDeLIhtAE4B8mglnnHc4TQ0y2p18xGYB/image.jpg' />
    <img src='https://www.evernote.com/l/AOL5TcAn3etDoIVK2I-vhWnPS5SPm6x7ta0B/image.jpg' />
    <img src='https://www.evernote.com/l/AOIBO8Eam1hJhqvMqRljvm5eqyOMEe858wkB/image.jpg' />
    <img src='https://www.evernote.com/l/AOLN-IoyOZhCVY-lsOUMwsk4NDRwVlkJoIYB/image.jpg' />
  </div>
  
  <div class='btns'>
    <button class='btn-prev'><</button>
    <button class='btn-next'>></button>
  </div>
  
  <script>${script}</script>
</body>

scroll-snap を活用しています!

main.css
.carousel {
  display: flex;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
}

.carousel img {
  width: 100vw;
  height: 60vh;
  object-fit: cover;
  scroll-snap-align: start;
}

.carousel::-webkit-scrollbar {
  display: none;
}

.btns {
  display: flex;
  justify-content: center;
  padding: 8px;
}

.btns button {
  margin: 0px 4px;
}

scrollIntoView() 関数を使っています!

main.js
class Carousel {
  // 初期化
  constructor(query) {
    this.$elm = document.querySelector(query);
    
    this.maxIndex = Math.round(this.$elm.scrollWidth / this.$elm.clientWidth);
  }
  
  // 今の index を取得
  get index() {
    var index = Math.round(this.$elm.scrollLeft / this.$elm.clientWidth);
    return index;
  }
  
  // 指定した場所に移動
  goto(index) {
    var i = (index + this.maxIndex) % this.maxIndex;
    this.$elm.children[i].scrollIntoView({ behavior: "smooth" });
  }
  
  // 次へ
  next() {
    this.goto(this.index+1);
  }
  
  // 前へ
  prev() {
    this.goto(this.index-1);
  }
}

window.onload = function() {
  // カルーセルを生成
  var carousel = new Carousel('.carousel');
  
  // ボタンのセットアップ
  var $btnPrev = document.querySelector('.btn-prev');
  var $btnNext = document.querySelector('.btn-next');
  
  $btnPrev.onclick = () => { carousel.prev(); };
  $btnNext.onclick = () => { carousel.next(); };
};

解説

CSS の scroll-snap を使ってスナップを実装しよう

scroll-snap というプロパティを活用すれば簡単にスクロールをピタッと指定した位置で止めることができるようになります.
2018年後半頃から使えるようになったプロパティで, iPhone のホーム画面のような UI を CSS だけで簡単に表現できるようになった感じです.

今回のサンプルでは, まず親要素に対して scroll-snap-typex mandatory を指定しています.
こうすることで横軸に対して mandatory が効くようになり, 必ず scroll-snap-align で指定した位置までスクロールして止まるようになります.

.carousel {
  display: flex;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
}

子要素側で, scroll-snap-align を指定することでスナップで止まる位置を決めています.
今回は start, つまり左端を基準にスナップするよう設定しています.

.carousel img {
  width: 100vw;
  height: 60vh;
  object-fit: cover;
  scroll-snap-align: start;
}

スクロールバーを非表示にしよう

これは webkit 限定になってしまうんですが以下のスタイルで簡単にスクロールバーを非表示にできます.

.carousel::-webkit-scrollbar {
  display: none;
}

クラスを定義しよう

CSS ではなく JavaScript のクラスですね.
今回は query を渡すと, それに紐付けてカルーセルの機能を使えるようにしたクラスを定義してみました.

class Carousel {
  // 初期化
  constructor(query) {
    this.$elm = document.querySelector(query);
    
    ...
}

キモとなる部分は↓で説明します.

scrollIntoView() でスムーズスクロールしよう

< > のボタンを押したときにスムーズにスクロールさせるために scrollIntoView() というメソッドを使っています.
これもわりと新し目のメソッドで, 子要素のこのメソッドを実行すると親要素内のスクロールがこの要素が見える位置までスクロールしてくれます.

更に引数のオブジェクトに behavior: "smooth" を指定するとスムーズスクロールしてくれます!!
すごい便利ですね♪

this.$elm.children[i].scrollIntoView({ behavior: "smooth" });

ボタンでスクロールできるようにしよう

ここは単純に JavaScript でクリックイベントを登録して, その中でそれぞれ prev/next メソッドを実行しています.

// ボタンのセットアップ
var $btnPrev = document.querySelector('.btn-prev');
var $btnNext = document.querySelector('.btn-next');

$btnPrev.onclick = () => { carousel.prev(); };
$btnNext.onclick = () => { carousel.next(); };

おわりに

今回紹介した機能は, モダンと言いつつ実は数年前から使える技術ではあるんですが, わりと知られてなかったりブラウザによっては動くようになったのが最近だったりするのでこのタイミングで書かせていただきました.

昔は jQeruy や HTML5 Canvas とかで頑張って表現していたことが標準機能で簡単にできるようになったのは嬉しいですがちょっとちょっと感慨深いですw

以上, 参考になれば幸いです.

ref

https://developer.mozilla.org/ja/docs/Web/CSS/scroll-snap-type

https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView

Discussion