【更新日 :

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

Category: css / js

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

ソースコード

デモ


See the Pen
GSAP Auto Slider
by Kazuma Sakata (@sakata-kazuma)
on CodePen.


使い方

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;
  }
}

JS読み込み

JSのソースコードを参照したい場合はこちらに記載しています。

<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>

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>

スライダー呼び出し

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 最新版

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);
}
}

関連リンク

Category : css / js