みかづきブログ・カスタム

基本的にはちょちょいのほいです。

scroll-snapを使ってカルーセルをつくる 🖱

CSSのscroll-snapをつかってカルーセルをつくってみました。

developer.mozilla.org

ループ無し


DEMO


ソースコード(抜粋)

JavaScript
const { useEffect, useRef, useState } = React;

function App() {
  const [ list ] = useState([1, 2, 3]);
  const width = 200;

  return (
    <div className="carousel">
      <ul>
        {list.map((item, i) => {
          return (
            <li
              key={ i }
              data-index={ item }
            >{ item }</li>
          );
        })}
      </ul>
    </div>
  );
}
SCSS
.carousel {
  width: 200px; height: 200px;
  overflow: scroll;
  cursor: pointer;
  scroll-snap-type: x mandatory;

  ul {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 300%; height: 100%;
  }
  
  li {
    display: flex;
    align-items: center;
    justify-content: center;
    width: calc(100% / 3); height: 100%;
    color: #ECEFF1;
    font-size: 80px;
    scroll-snap-align: center;

    &[data-index='1'] {
      background: #B71C1C;
    }

    &[data-index='2'] {
      background: #311B92;
    }

    &[data-index='3'] {
      background: #004D40;
    }
  }
}

解説

ループ無し、つまり例で言うところの①と③を繋げなくて良いのであればものすごくシンプルです。

  1. overflow: scrollを掛けた要素に、scroll-snap-type: x mandatory (横方向にスクロールする場合)を掛ける
  2. スナップを効かせたい要素に、scroll-snap-align: center (中心合わせの場合)を掛ける

の2点でOKです。
例ではReactを使っていますが、Reactを使う必要がないぐらいシンプルです。


ループあり


DEMO


ソースコード(抜粋)

Javascript
const { useEffect, useRef, useState } = React;

function App() {
  const [ list, setList ] = useState([3, 1, 2]);
  const timerRef = useRef(-1);
  const carouselRef = useRef();
  const width = 200;

  useEffect(() => { // リストが入れ替わった際に真ん中の要素までスクロールする
    carouselRef.current.scrollLeft = width;
  }, [list]);

  function handleScroll() {
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => { // スクロールが終わってから0.1秒後に発火
      if (width * (list.length - 1) === carouselRef.current.scrollLeft) { // 右端到達時
        setList([ list[1], list[2], list[0] ]); // リストを入れ替える
      } else if (0 === carouselRef.current.scrollLeft) { // 左端到達時
        setList([ list[2], list[0], list[1] ]); // リストを入れ替える
      }
    }, 100);
  }

  return (
    <div
      ref={ carouselRef }
      onScroll={ handleScroll }
      className="carousel"
     >
      <ul>
        {list.map((item, i) => {
          return (
            <li
              key={ i }
              data-index={ item }
            >{ item }</li>
          );
        })}
      </ul>
    </div>
  );
}
SCSS
.carousel {
  width: 200px; height: 200px;
  overflow: scroll;
  cursor: pointer;
  scroll-snap-type: x mandatory;
  -ms-overflow-style: none; // スクロールバー非表示
  scrollbar-width: none; // スクロールバー非表示

  &::-webkit-scrollbar {
    display: none; // スクロールバー非表示
  }

  ul {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 300%; height: 100%;
  }
  
  li {
    display: flex;
    align-items: center;
    justify-content: center;
    width: calc(100% / 3); height: 100%;
    color: #ECEFF1;
    font-size: 80px;
    scroll-snap-align: center;

    &[data-index='1'] {
      background: #B71C1C;
    }

    &[data-index='2'] {
      background: #311B92;
    }

    &[data-index='3'] {
      background: #004D40;
    }
  }
}

解説

スクロールイベントを監視して、右端に来た際、左端に来た際にリストを入れ替えることで、無限にスクロールできるように改良しています。
また、スクロールバーがあるとノイズになるのでCSSの力で非表示にしました。

blog.kimizuka.org