Web production note

 【更新日 :

【CSS , JavaScript】GSAPで実装する横スクロールの自動スライダー

Category:
css / js

GSAPとJavaScriptで実装する横スクロールのスライダーのサンプルです。
画像にはパララックス効果が入っています。

ソースコード

GitHub

動作サンプル

codepen

See the Pen GSAP Auto Slider by web_nak (@web_walking_nak) on CodePen.

HTML

<div class="js-auto-slider-wrap">
  <div class="js-auto-slider-inner">
    <div class="js-auto-slider" id="js-auto-slider">
      <div class="js-auto-slider-elm">
        <div class="js-auto-slider-img">
          <img src="https://picsum.photos/id/101/1740/720" alt="">
        </div>
      </div>
      <div class="js-auto-slider-elm">
        <div class="js-auto-slider-img">
          <img src="https://picsum.photos/id/1015/1740/720" alt="">
        </div>
      </div>
      <div class="js-auto-slider-elm">
        <div class="js-auto-slider-img">
          <img src="https://picsum.photos/id/109/1740/720" alt="">
        </div>
      </div>
      <div class="js-auto-slider-elm">
        <div class="js-auto-slider-img">
          <img src="https://picsum.photos/id/110/1740/720" alt="">
        </div>
      </div>
    </div>
  </div>
</div>

CSS

<link rel="stylesheet" href="file-path/auto-slider.min.css">

もしくは以下styleを追加してください。

/* auto slider ベース設定 */
.js-auto-slider-wrap {
  overflow: hidden;
}
.js-auto-slider {
  display: flex;
}
.js-auto-slider-elm {
  overflow: hidden;
  position: relative;
	z-index: 1;
  flex-shrink: 0;
}
.js-auto-slider-img {
  position: absolute;
  top: 0;
  left: 0;
	right: -45%;
  bottom: 0;
}
.js-auto-slider-img img {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  object-fit: cover;
  font-family: 'object-fit: cover;';
}


/* auto slider 表示エリア */
.js-auto-slider-inner {
  width: 77vw;
  margin-left: auto;
  margin-right: auto;
}
/* auto slider スライド要素 */
.js-auto-slider-elm {
  height: 50vw;
  width: 76vw;
  margin-right: 4.8vw;
  border-radius: 3.3vw;
}
@media only screen and (min-width: 768px) {
  .js-auto-slider-inner {
    width: 60vw;
  }
  .js-auto-slider-elm {
    height: 30vw;
    width: 50vw;
    margin-right: 5vw;
    border-radius: 1.5vw;
  }
}

JavaScript

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="file-path/auto-slider.min.js"></script>

※IE11へ対応する場合は auto-slider-ie11.min.js を読み込んでください。

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="file-path/auto-slider-ie11.min.js"></script>

auto-slider.min.jsを読み込ませない場合は以下のコードを追加してください。

//auto slider
function autoSlider(setOptions) {
  'use strict';

  //デフォルト設定
  const defaultOptions = {
    target: null,  //スライダー ターゲット設定
    activeClass: 'is-active',  //スライダー要素 active class
    slideElm: 'js-auto-slider-elm',  //スライダー要素 class
    slideElmClone: 'js-auto-slider-elm-clone',  //スライダー複製要素 class
    slideChildImg: 'js-auto-slider-img',  //スライダー画像要素 class
    activeIndex: 0,  //開始するスライド番号
    scrollTimeBefore: 5,  //前半スクロール時間
    scrollTimeAfter: 0.6,  //後半スクロール時間
    scrollSpeedBefore: 30,  //前半スクロール量
    scrollRatioBefore: 0.4,  //前半スクロール量
    easing: Power1.easeOut,  //後半スクロール GSAPイージング
    pager: null, //スライダーページャー ターゲット設定
    pagerBusy: 'is-busy', //スライダーページャー スライド動作中判別 class
    pagerElm: 'js-auto-slider-pager-elm', //スライダーページャー要素 class
    pagerBtn: 'js-auto-slider-pager-btn', //スライダーページャー要素内 button要素 class
    pagerText: null, //スライダーページャー要素内 button要素に格納するテキスト(デフォルトは index番号)
    pagerActiveClass: 'is-active', //スライダーページャー要素・button要素 active class
  }

  //設定をマージ
  const options = Object.assign({}, defaultOptions, setOptions);

  //動作用設定追加
  options.slideImgBoxAnime = null;
  options.startSlideIndex = 0;
  options.lastSlideIndex = 0;
  options.thisActiveElm = null;
  options.slideXAfter = 0;
  //設定値チェック
  if(options.scrollRatioBefore >= 0.9) {
    options.scrollRatioBefore = 0.8;
  }


  //要素チェック
  if(!document.querySelector(options.target)) {
    return false;
  }

  //要素を登録
  options.target = document.querySelector(options.target);

  //スライダー呼び出し
  slideFunc(options);
  let resizeTimer = null;
  window.addEventListener('resize', function(){
    //リサイズチェック
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(function() {
      // リサイズ完了後の処理
      slideFunc(options);
    }, 200);
  });

  function slideFunc(options) {
    const target = options.target;

    //active class チェック
    if(!target.querySelector('.'+options.activeClass)) {
      //初期設定

      //アクティブIndex設定
      const activeIndex = options.activeIndex;
      target.setAttribute('data-active',activeIndex);

      //slide要素にIndex登録
      let slideElms = Array.prototype.slice.call(target.querySelectorAll('.'+options.slideElm),0);
      const slideLength = slideElms.length;
      //スライドが1枚の時はスライダーを起動しない
      if(slideLength < 2) {
        return false;
      }
      slideElms.forEach(function(elm, index){
        elm.setAttribute('data-slide-index',index);
      });

      //ページャー生成
      const pager = document.querySelector(options.pager);
      if(pager) {
        let setHtml = '';
        let setClass = '';
        for (let i = 0; i < slideLength; i++) {
          if(i !== activeIndex) {
            setClass = '';
          } else {
            setClass = ' ' + options.pagerActiveClass;
          }
          let setText = options.pagerText;
          if(!setText) {
            setText = i;
          }
          setHtml += '<li class="'+options.pagerElm + setClass+'"><button class="'+options.pagerBtn+setClass+'" data-slide-index="'+i+'">'+setText+'</button></li>';
        }
        pager.innerHTML = setHtml;
      }

      //スライド数を3以上にする
      const slideElmsHtml = target.innerHTML;
      while (target.querySelectorAll('.'+options.slideElm).length < 3) {
        target.innerHTML += slideElmsHtml;
      }

      ///先頭にダミーhtml 追加
      slideElms = target.querySelectorAll('.'+options.slideElm);
      let lastSlideElm = slideElms[slideElms.length-1];
      const cloneElm = lastSlideElm.cloneNode(true);
      cloneElm.classList.add(options.slideElmClone);
      target.insertBefore(cloneElm, slideElms[0]);

      //開始位置取得
      options.startSlideIndex = options.startSlideIndex + 1;

      //スライド終了番号取得
      options.lastSlideIndex = target.querySelectorAll('.'+options.slideElm).length - 1;

      ///後方にダミーhtml 追加
      const cloneElms = Array.prototype.slice.call(slideElms,0);
      cloneElms.forEach(function(elm, index){
        const cloneElm = elm.cloneNode(true);
        cloneElm.classList.add(options.slideElmClone);
        target.insertBefore(cloneElm, null);
      });

      //active class 追加
      target.querySelectorAll('.'+options.slideElm)[(options.startSlideIndex + activeIndex)].classList.add(options.activeClass);
      options.thisActiveElm = target.querySelector('.'+options.activeClass);

      //ページャークリック
      if(pager) {
        //clickイベント
        let pagerBtns = Array.prototype.slice.call(pager.querySelectorAll('.'+options.pagerBtn),0);
        pagerBtns.forEach(function(elm, index){
          elm.addEventListener('click', function() {
            //ページャーの番号取得
            const thisIndex = Number(elm.getAttribute('data-slide-index'));

            //現在のアクティブ要素の番号
            const getActiveIndex = options.activeIndex;
            //現在のアクティブ要素が何枚目のスライドか
            const activeSlideIndex =  Number(target.querySelectorAll('.'+options.slideElm)[getActiveIndex].getAttribute('data-slide-index'));

            //現在の番号 or スライド切り替え中だった場合
            if(thisIndex === activeSlideIndex || pager.classList.contains(options.pagerBusy)) {
              return false;
            }

            //スライド動作中判別class追加
            pager.classList.add(options.pagerBusy);

            //ページャー active class削除
            removePagerClass(pager,options);

            //後半スライド位置
            //スライド幅取得
            const slideWidth = getWidth(options.thisActiveElm);

            //次のスライド番号
            let nextSlideIndex = getActiveIndex + thisIndex - activeSlideIndex;
            if(thisIndex < activeSlideIndex) {
              //前に戻る
              const getSlideLength = options.lastSlideIndex;
              nextSlideIndex = getSlideLength + thisIndex + 1;
            }

            //現在のアクティブ番号を設定
            target.setAttribute('data-active',  thisIndex);

            //スライドアニメーション
            options.slideXAfter = slideWidth * nextSlideIndex;

            gsap.to(target, options.scrollTimeAfter, {
              x: -options.slideXAfter,
              ease: options.easing,
              overwrite: true,
              onComplete: function() {
                const slideElms = target.querySelectorAll('.'+options.slideElm);
                let getActiveIndex = Number(target.getAttribute('data-active'));

                //actveスライド切り替え
                options.thisActiveElm.classList.remove(options.activeClass);

                if(thisIndex < activeSlideIndex) {
                  //先頭のスライドへclassを戻す
                  let targetIndex = options.startSlideIndex+thisIndex;
                  slideElms[targetIndex].classList.add(options.activeClass);

                  //1枚目
                  gsap.set(slideElms[targetIndex-1].querySelector('.'+options.slideChildImg), {
                    x: gsap.getProperty(slideElms[options.lastSlideIndex].querySelector('.'+options.slideChildImg), 'x')+'%',
                    overwrite: true
                  });
                  //2枚目
                  gsap.set(slideElms[targetIndex].querySelector('.'+options.slideChildImg), {
                    x: gsap.getProperty(slideElms[options.lastSlideIndex+1].querySelector('.'+options.slideChildImg), 'x')+'%',
                    overwrite: true
                  });
                  //3枚目
                  gsap.set(slideElms[targetIndex+1].querySelector('.'+options.slideChildImg), {
                    x: gsap.getProperty(slideElms[options.lastSlideIndex+2].querySelector('.'+options.slideChildImg), 'x')+'%',
                    overwrite: true
                  });

                } else {
                  //次のスライドへclassを切り替え
                  slideElms[(options.startSlideIndex+getActiveIndex)].classList.add(options.activeClass);
                }

                //アクティブ要素更新
                options.thisActiveElm = target.querySelector('.'+options.activeClass);

                //アニメーション呼び出し
                slideAnimeFunc(options);

                //スライド動作中判別class削除
                pager.classList.remove(options.pagerBusy);
                addPagerClass(pager,options);
              }
            });
          });
        });
      }
    }

    //スライダーセットアップ
    if(options.slideImgBoxAnime) {
      options.slideImgBoxAnime = null;
    }
    //スライダー呼び出し
    slideAnimeFunc(options);
  }
  function slideAnimeFunc(options) {
    const target = options.target;

    //アクティブスライドの番号を取得
    let slideElms =  Array.prototype.slice.call(target.querySelectorAll('.'+options.slideElm),0);
    options.thisActiveElm = target.querySelector('.'+options.activeClass);
    options.activeIndex = slideElms.indexOf(options.thisActiveElm);

    //アクティブスライドの番号を設定
    target.setAttribute('data-active',options.activeIndex);

    //スライド幅取得
    const slideWidth = getWidth(options.thisActiveElm);

    //開始位置 取得
    const startPosi = -(slideWidth * options.activeIndex);
    //開始位置 設定
    options.slideImgBoxAnime = gsap.set(target,{
      x: startPosi
    });

    //後半スクロール位置
    options.slideXAfter = slideWidth * (options.activeIndex + 1);

    //前半スクロール量
    let slideBeforeWidth = slideWidth * options.scrollRatioBefore;

    //定速スクロール量
    const linearScrollX = options.scrollTimeBefore * options.scrollSpeedBefore;
    if(slideBeforeWidth > linearScrollX) {
      slideBeforeWidth = linearScrollX;
    }
    const slideBeforeX = -(options.slideXAfter - (slideWidth - slideBeforeWidth));

    //前半・定速スクロール
    options.slideImgBoxAnime = gsap.to(target, options.scrollTimeBefore, {
      x: slideBeforeX,
      ease:Linear.easeNone,
      overwrite: true,
      onComplete: function() {
        //スライド動作中判別class追加
        const pager = document.querySelector(options.pager);
        if(pager) {
          pager.classList.add(options.pagerBusy);
        }
        //後半アニメーション呼び出し
        slideAnimeAfter(options);
      }
    });


    //アクティブ画像以外はパララックスリセット
    const slideElmImgs =  Array.prototype.slice.call(target.querySelectorAll('.'+options.slideChildImg),0);
    gsap.to(slideElmImgs, options.scrollTimeAfter, {
      x: '0%',
      ease:Linear.easeNone,
      overwrite: true
    });

    //アクティブ画像スライド 前後3枚
    let setActiveImg = [];
    setActiveImg.push(options.thisActiveElm.previousElementSibling.querySelector('.'+options.slideChildImg));
    setActiveImg.push(options.thisActiveElm.querySelector('.'+options.slideChildImg));
    setActiveImg.push(options.thisActiveElm.nextElementSibling.querySelector('.'+options.slideChildImg));

    gsap.to(setActiveImg, options.scrollTimeBefore, {
      x: '+=-3%',
      ease:Linear.easeNone,
      overwrite: true,
      onComplete: function() {
        gsap.to(setActiveImg, options.scrollTimeAfter, {
          x: '+=-10%',
          ease: options.easing,
          overwrite: true
        });
      }
    });

  }

  function slideAnimeAfter(options) {
    const target = options.target;
    const slideElms = target.querySelectorAll('.'+options.slideElm);
    gsap.to(target, options.scrollTimeAfter, {
      x: -options.slideXAfter,
      ease: options.easing,
      overwrite: true,
      onComplete: function() {
        let getActiveIndex = Number(target.getAttribute('data-active'));

        //actveスライド切り替え
        options.thisActiveElm.classList.remove(options.activeClass);

        if(getActiveIndex < options.lastSlideIndex) {
          //次のスライドへclassを切り替え
          slideElms[(options.startSlideIndex+getActiveIndex)].classList.add(options.activeClass);

        } else {
          //先頭のスライドへclassを戻す
          slideElms[options.startSlideIndex].classList.add(options.activeClass);

          //画像リセット
          //1枚目
          gsap.set(slideElms[options.startSlideIndex-1].querySelector('.'+options.slideChildImg), {
            x: gsap.getProperty(slideElms[options.lastSlideIndex].querySelector('.'+options.slideChildImg), 'x')+'%',
            overwrite: true
          });

          //2枚目
          gsap.set(slideElms[options.startSlideIndex].querySelector('.'+options.slideChildImg), {
            x: gsap.getProperty(slideElms[options.lastSlideIndex+1].querySelector('.'+options.slideChildImg), 'x')+'%',
            overwrite: true
          });
          //3枚目
          gsap.set(slideElms[options.startSlideIndex+1].querySelector('.'+options.slideChildImg), {
            x: gsap.getProperty(slideElms[options.lastSlideIndex+2].querySelector('.'+options.slideChildImg), 'x')+'%',
            overwrite: true
          });
        }

        //アクティブ要素更新
        options.thisActiveElm = target.querySelector('.'+options.activeClass);

        //アニメーション呼び出し
        slideAnimeFunc(options);

        //ページャー切り替え設定
        const pager = document.querySelector(options.pager);
        if(pager) {
          //スライド動作中判別class削除
          pager.classList.remove(options.pagerBusy);
          removePagerClass(pager,options);
          addPagerClass(pager,options);
        }
      }
    });
  }
  function getWidth(elm) {
    //幅取得
    const width = elm.getBoundingClientRect().width;
    //margin取得
    const styles = getComputedStyle(elm);
    const left = parseFloat(styles.marginLeft);
    const right = parseFloat(styles.marginRight);
    return width + left + right;
  }
  function removePagerClass(pager,options) {
    //active class削除
    pager.querySelector('.'+options.pagerElm+'.'+options.pagerActiveClass).classList.remove(options.pagerActiveClass);
    pager.querySelector('.'+options.pagerBtn+'.'+options.pagerActiveClass).classList.remove(options.pagerActiveClass);
  }
  function addPagerClass(pager,options) {
    //active class設定
    const activeSlideIndex =  Number(options.thisActiveElm.getAttribute('data-slide-index'));
    const activePager = pager.querySelectorAll('.'+options.pagerElm)[activeSlideIndex];
    activePager.classList.add(options.pagerActiveClass);
    activePager.querySelector('.'+options.pagerBtn).classList.add(options.pagerActiveClass);
  }
}

スライダー呼び出し

autoSlider({
  target: '#js-auto-slider'
});

オプション

ページャー追加

任意の場所にページャー用HTMLを追加

<ul class="js-auto-slider-pager" id="js-auto-slider-pager"></ul>

スライダー呼び出しコードにページャーオプションを追加

autoSlider({
  target: '#js-auto-slider',
  pager: '#js-auto-slider-pager'
});

その他オプション

autoSlider({
  target: null,  //スライダー ターゲット設定
  activeClass: 'is-active',  //スライダー要素 active class
  slideElm: 'js-auto-slider-elm',  //スライダー要素 class
  slideElmClone: 'js-auto-slider-elm-clone',  //スライダー複製要素 class
  slideChildImg: 'js-auto-slider-img',  //スライダー画像要素 class
  activeIndex: 0,  //開始するスライド番号
  scrollTimeBefore: 5,  //前半スクロール時間
  scrollTimeAfter: 0.6,  //後半スクロール時間
  scrollSpeedBefore: 30,  //前半スクロール量
  scrollRatioBefore: 0.4,  //前半スクロール量
  easing: Power1.easeOut,  //後半スクロール GSAPイージング
  pager: null, //スライダーページャー ターゲット設定
  pagerBusy: 'is-busy', //スライダーページャー スライド動作中判別 class
  pagerElm: 'js-auto-slider-pager-elm', //スライダーページャー要素 class
  pagerBtn: 'js-auto-slider-pager-btn', //スライダーページャー要素内 button要素 class
  pagerText: null, //スライダーページャー要素内 button要素に格納するテキスト(デフォルトは index番号が入ります)
  pagerActiveClass: 'is-active', //スライダーページャー要素・button要素 active class
});

動作環境

  • Internet Explorer 11
  • Google Chrome 最新版
  • Firefox 最新版
  • Safari 最新版

関連リンク