Web production note

 【更新日 :

【Web Animations API】JavaScriptで要素数が変わっても一定速度で動くスクロールスライダーを実装する

Category:
css / js

Web Animations APIで要素数が変わっても一定の速度を保つ等速スクロールスライダーを実装したサンプルです。
CSSなど横スクロールに時間を指定するものは要素数が変わるとスピードも変わってしまいますが、要素数がバラバラでも一定の速度を保って動きます。

codepen

See the Pen Constant velocity scroll slider independent of number Vanilla JS (Web Animations API) by web_nak (@web_walking_nak) on CodePen.

JavaScript

//要素数が変わっても一定の速度を保つ等速スクロールスライダー
const scrollSlider = document.querySelectorAll('.js-scroll-slider');
if(scrollSlider.length > 0) {
  //スクロールスピード設定
  const speed = 0.05;

  scrollSlider.forEach((slider) => {
    //スライド要素を取得
    const children = slider.children;
    const childLength = children.length;
    //スライド要素一式を文字列で取得
    let baseChildren = '';
    for (let i = 0; i < children.length; i++) {
      baseChildren += children[i].outerHTML;
    }

    //スライド要素の横幅を取得
    const firstChild = slider.firstElementChild;
    const styles = getComputedStyle(firstChild);
    let width = parseFloat(styles.width);
    let marginRight = parseFloat(styles.marginRight);

    //スライダーの途切れ対策
    let winWidth = window.innerWidth;
    let checkWidth = winWidth * 2;
    let sliderWidth = (width + marginRight) * childLength;

    let countWidth = 0;
    let addCount = 1;
    while(countWidth < checkWidth) {
      slider.insertAdjacentHTML('beforeend',baseChildren);
      ++addCount;
      countWidth = sliderWidth * addCount;
    }

    //反転処理
    let unit = '-';
    const isReverse = slider.classList.contains('is-reverse');
    if(isReverse) {
      unit = '';
      slider.style.marginLeft = '-' + (countWidth - winWidth - marginRight) + 'px';
    }

    //アニメーションセット
    const keyframes = {
      transform: 'translateX('+ unit + sliderWidth + 'px)'
    };
    const timing = {
      fill: 'backwards',
      duration: sliderWidth / speed,
      easing: 'linear',
      iterations: Infinity
    };
    let slideAnime = slider.animate(keyframes,timing);


    //レスポンシブ対応
    let timeoutId;
    let lastWinWidth = window.innerWidth ;
    window.addEventListener('resize', () => {
      clearTimeout(timeoutId);

      //リサイズを停止した時のみ処理
      timeoutId = setTimeout(() => {
        // 現在と前回の横幅が違う場合だけ実行
        if (lastWinWidth !== window.innerWidth) {
          lastWinWidth = window.innerWidth;
          winWidth = lastWinWidth;

          //スライダーが途切れないかチェック
          width = parseFloat(styles.width);
          marginRight = parseFloat(styles.marginRight);
          sliderWidth = (width + marginRight) * childLength;
          checkWidth = winWidth * 2;
          countWidth = sliderWidth * addCount;
          while(countWidth < checkWidth) {
            slider.insertAdjacentHTML('beforeend',baseChildren);
            ++addCount;
            countWidth = sliderWidth * addCount;
          }

          //設定をリセット
          if(isReverse) {
            slider.style.marginLeft = '-' + (countWidth - winWidth - marginRight) + 'px';
          }
          slideAnime.effect.updateTiming({iterations:1});
          slideAnime.finish();

          //スライダー再定義
          keyframes.transform = 'translateX('+ unit + sliderWidth + 'px)';
          timing.duration = sliderWidth / speed;
          slideAnime = slider.animate(keyframes,timing);
        }
      }, 500);
    });
  });
}

HTML

<div class="wrapper">
  <div class="js-scroll-slider">
    <!-- スライド要素 -->
    <div class="js-scroll-slider__item"><img src="" alt=""></div>
    <div class="js-scroll-slider__item"><img src="" alt=""></div>
  </div>

  <!-- 逆方向の場合は is-reverse を追加 -->
  <div class="js-scroll-slider is-reverse">
    <!-- スライド要素 -->
    <div class="js-scroll-slider__item"><img src="" alt=""></div>
    <div class="js-scroll-slider__item"><img src="" alt=""></div>
  </div>
</div>

CSS

アニメーション中は要素が画面外へ出ますので、外側に overflow: hidden; を必ずかけてください。

.wrapper {
  overflow: hidden;
}
.js-scroll-slider {
  display: flex;
  margin-bottom: 20px;
}
.js-scroll-slider__item {
  flex-shrink: 0;
  width: 400px;
  margin-right: 20px;
}