2020.06.30

JQueryで多機能スライダーを一から実装してみた【プラグインなし】

 

※この記事を読む前に、下に本記事の改訂版のリンクを貼っておくのでそちらを先に読んでください。
読んだ後、再度この記事にあるコードと照らし合わせて見てください。

この記事は、リファクタリングの重要性を知るのにもってこいだと思い、敢えて消していません。
以下の記事と照らし合わせて見てみると楽しいです。

【jQuery】プラグインなしの多機能スライダーをもう一度作成してみた【改訂版】


JSでスライダーを実装

今回は多機能スライダーを実装してみました。プラグインを使えば一瞬で実装できるのですが、勉強の一環として、あえて一からロジックを理解しながら実装してみました。

全部理解するのにかなり時間を要しました、おおよそ落とし込めた感はあるのでアウトプットしていこうと思います。

機能としてはよく見るものばかりですが、よく見るということは需要があるということで、ここら辺を抑えておけば今後のjQuery学習も楽になりそう。

こんなやつ↓

実装する機能

・前ボタン、次ボタンの実装(ナビゲーション)

・スライド総数、現在地、ドットクリックで対象スライドを表示するインジケーターの実装

・無限スライダー(最前スライドから最背スライドへのスムーズな切り替わり)

時間経過でスライドを移動させる機能 ※情報が多いので割愛

まずは全体のコードを載せておきます。自分もjQueryに手を付けたのは最近なので冗長なコードではあります。もし致命的なところ等ありましたらコメントください!

後半からコードの処理内容を解説していきますので、どんな処理をしているのかざっと見てみてください。

HTML

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>sliderの練習</title>
    <link rel="stylesheet" href="./css/reset.css">
    <link rel="stylesheet" href="./css/slider.css">
    <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
    <script src="./js/vendor/modernizr.custom.min.js"></script>
    <script src="./js/vendor/jquery-1.10.2.min.js"></script>
    <script src="./js/vendor/jquery-ui-1.10.3.custom.min.js"></script>
    <script src="./js/slider.js"></script>
  </head>
  <body>
    <div class="slider">
      <div class="slideSet">
        <div class="slide">
          <img src="img/yui2trim.jpg">
        </div>
        <div class="slide">
          <img src="img/yui1trim.jpg">
        </div>
        <div class="slide">
          <img src="img/shiki3trim.jpg">
        </div>
        <div class="slide">
          <img src="img/shuko9trim.jpg">
        </div>
        <div class="slide">
          <img src="img/shuko4trim.jpg">
        </div>
      </div>
      <div class="buttonWrap">
        <button class="btn slider-prev">
          <i class="fas fa-angle-left fa-2x"></i>
        </button>
        <button class="btn slider-next">
        <i class="fas fa-angle-right fa-2x"></i>
        </button>
      </div>
      <div class="indicator">
      </div>
    </div>
  </body>
</html>

HTMLの説明は省きます。

CSS

img {
  vertical-align: bottom;
}

.slider {
  overflow: hidden;
  height: 500px;
  max-width: 1280px;
  width: 100%;
  margin: auto;
  position: relative;
}

.slider .slideSet {
  position: absolute;
  height: auto;
  white-space: nowrap;
}

.slider .slide {
  float: left;
  overflow: hidden;
}

.buttonWrap {
  display: flex;
  justify-content: space-between;
  width: 90%;
  position: relative;
  margin: auto;
  top: 200px;
}

.btn {
  width: 40px;
  height: 40px;
  border-style: none;
  background-color: #fff;
  border-radius: 50%;
  opacity: 0.6;
}

.btn:focus {
  outline:0;
}

.slider-prev {
  padding-right: 10px;
}

.slider-next {
  padding-left: 10px;
}

.indicator {
  width: auto;
  padding: 0 30px;
  display: flex;
  justify-content: center;
  position: relative;
  opacity: 0.9;
}

.dot {
  border-radius: 50%;
  background-color: #333;
  width: 20px;
  height: 20px;
  border:  solid 2px #fff;
}

.dot + .dot{
  margin-left: 15px;
}

JS

$(function () {

    var slideWidth = $('.slide').outerWidth(); // .slideの幅を取得して代入
    var slideNum = $('.slide').length; // .slideの数を取得して代入
    var slideCount = 1;  //slideの初期値
    var indicator = $(".indicator");
    var indicatorHTML = "";
    var dotIndex = 1;
    var slideSetWidth = slideWidth * (slideNum + slideWidth * 3); // .slideの幅×数で求めた値を代入
    $('.slideSet').css('width', slideSetWidth); // .slideSetのCSSにwidth: slideSetWidthを指定
    var clickFlag = true;
    $(".slideSet").stop(true).animate({
        left: slideCount * -slideWidth
    },0);

    //インジケーターの生成
    $(".slide").each(function (){
        indicatorHTML += `<div class="dot" id="${dotIndex}">` + '</div>';
        indicator.html(indicatorHTML);
        dotIndex++;
    });

    //インジケーター初期位置
    $(".dot:first-child").css({
        backgroundColor: "#fff"
    });

    //最初のスライドを複製してslideSet内最後に付け加える
    function cloneSlide() {
        $(".slideSet").find(".slide:last-child").clone(true).prependTo($(".slideSet"));
        $(".slideSet").find(".slide:nth-child(2)").clone(true).appendTo($(".slideSet"));
    }

    //インジケーター
    $(".dot").click(function(){
        //クリックされたドットの特定
        var idname = $(this).attr("id");
        var currentDot = $(`.dot#${idname}`);
        $(".dot").css({
            backgroundColor: "#333"
        });
        currentDot.css({
            backgroundColor: "#fff"
        });
        //クリックされたドット位置とスライド位置を揃える
        slideCount = idname;
        $(".slideSet").stop(true).animate({
            left: slideCount * -slideWidth
        });
    });

    //ドットの色
    function dotColor(){ //currentDot→slideCount
        if(slideCount > slideNum){
            currentDot = $(`.dot#${idname-5}`);
        }else if(slideCount == 0){
            currentDot = $(`.dot#5`);
        }else{
            currentDot = $(`.dot#${idname}`);
        }
        $(".dot").css({
            backgroundColor: "#333"
        });
        currentDot.css({
            backgroundColor: "#fff"
        });
    }

    //変数slidingにアニメーション効果を格納
    function sliding() {
        var duration = 500;

            //スライドの移動処理
            $(".slideSet").stop(true).animate({
                left: slideCount * -slideWidth
            },duration,function() {
                clickFlag = true;
            });
            console.log("reset" + slideCount);


            
            //slideCountが1以下だった場合
            if(slideCount < 1){
                //スライド1枚目から6枚目に移動(アニメーション0秒)
                slideCount = 5;
                console.log(slideCount);
                delayedCall(0.5,function(){
                    $(".slideSet").animate({
                        left: slideCount * -slideWidth
                    },0);
                });

                //slideCountがslideNum上限に差し掛かった場合
                }else if(slideCount == slideNum){
                    console.log("next"+slideCount);
                    slideCount = 0;
                    delayedCall(0.5,function(){
                        $(".slideSet").animate({
                            left: slideCount * -slideWidth
                        },0);
                    });
                }
        function delayedCall(second, callBack){
            setTimeout(callBack, second * 1000);
        }
    }

    //nextボタン押下でsliding実行
    function sliderNext(){
        $(".slider-next").click(function(){
            if(clickFlag){
                clickFlag = false;
                slideCount++;
                sliding();
                idname = slideCount;
                console.log("idname" + idname);
                console.log("slideNum" + slideNum);
                console.log("slideCount" + slideCount);
                dotColor();
            }else{
                return false;
            }
        });
    }
    //prevボタン押下でsliding実行
    function sliderPrev(){
        $(".slider-prev").click(function(){
            if(clickFlag){
                clickFlag = false;
                slideCount--;
                sliding();
                idname = slideCount;
                console.log("idname" + idname);
                console.log("slideNum" + slideNum);
                console.log("slideCount" + slideCount);
                dotColor();
            }else{
                return false;
            }
        });
    }

    function init() {
        cloneSlide();
        sliding();
    }
    init();
    //イベント発火
    sliderNext();
    sliderPrev();
});

 

 

スライダーの構成

slideSetがスライドを格納しています。フィルムのような感じで横にスライドしていくイメージです。

必ずしもこのような構成ではないとは思いますが、これが基本的な形っぽいのでこれでいきます。

以降はかいつまんで重要な部分のみ説明していきます。

 

ナビゲーションの実装

ナビゲーションボタン押下で対象スライドに移動するようにしていきます。

var slideWidth = $('.slide').outerWidth(); // .slideの幅を取得して代入
var slideNum = $('.slide').length;  // .slideの数を取得して代入 lengthで対象の総数を取得できる

まず必要となる値ですが、slideの幅、slideSetの幅です。この値が移動幅になります。

以下で取得してきます。

var slideCount = 1;  //slideの初期値

$(".slideSet").stop(true).animate({
left: slideCount * -slideWidth
});

slideCountの初期値を1としている理由は、後半のほうの無限スライダーのところで説明します。

セレクタ".slideSet"をアニメーションさせる関数です。

 

 

ボタン連打の防止

ボタンを連打されると意図しない挙動になる場合があるので、アニメーション実行中は他イベントを発火させないようにします。

var clickFlag = true;

まずはフラグとなる変数を定義します。trueでイベントの実行が可能になり、flaseでイベントを無効化させます。

//nextボタン押下でsliding実行
functionsliderNext(){
    $(".slider-next").click(function(){
        if(clickFlag){
            clickFlag = false;
            slideCount++;
            sliding();
            idname = slideCount;
            dotColor();
         }else{
            return false;
        }
    });
}
こうすることでアニメーション実行中は他イベントは発火されないようになります。

無限スライダーの実装

ナビゲーションボタンの挙動は大まかに2パターンに分かれると思います。

・最前、最背スライドが現在地の場合、Prev、nextボタンを非表示にする

・無限スライド(最前から最背スライドへのなめらかな切り替わり(逆も同様))

無限スライダーっていってもイメージしずらいと思いますが、例えると昔のゲームみたいに画面端に行くと逆側の画面端から出てくるみたいな感じです。いまからもう少し説明していきます。

//最初に表示させるスライドを複製してslideSet内最後に付け加える
//最後に表示させるスライドを複製してslideSet内最初に付け加える
function cloneSlide() {
    $(".slideSet").find(".slide:last-child").clone(true).prependTo($(".slideSet"));
    $(".slideSet").find(".slide:nth-child(2)").clone(true).appendTo($(".slideSet"));
}

挙動の説明

最初に表示させたいスライドと最後に表示させたいスライドをclone(複製)します。cloneしたあと、appendTo,prependToしてslideSetに追加します。

cloneする理由

表示させたい画像を、前後に持ってくることで、滑らかな挙動の無限スライドを実現できます。

注意したいのが、クローン関数によってスライドのインデックスが表示させたいスライドと一個ずれます。これはクローンされた要素がappendToによって追加されるためです。

よって、cloneSlide4のインデックスは0slide1のインデックスは1になります。//var slideCount = 1;  //slideの初期値

prevの処理とnextの処理をif文で切り分けてうまい具合にインデックスを調整することで綺麗にループさせることができます。

簡単に図で表すとこんな感じです。

①の処理
図の通りそのままスライドさせる。ここでのスライド先はクローンしたスライドになります。そのまま続けて②の処理(if文内の処理)に入る。
②の処理
最初or最後のオリジナルのほうのスライドインデックスを指定します。
スライドアニメーションの関数をsetTimeout関数で囲み、ディレイ処理をかけてあげます。ディレイの時間は①のスライドアニメーション処理と同じ時間で設定します。
ここでのスライドアニメーションの時間は0秒とする。0秒とする理由は人間の目で視認できないようにクローンからオリジナルのスライドに移動したいため。
③の処理
上記の過程でオリジナルのスライドに戻れたのであとは普通のスライド処理に戻る。

 

以上を踏まえて再度コードを確認してみると理解しやすいと思います↓

    //変数slidingにアニメーション効果を格納
    function sliding() {
        var duration = 500;

            //スライドの移動処理
            $(".slideSet").stop(true).animate({
                left: slideCount * -slideWidth
            },duration,function() {
                clickFlag = true;
            });

            //slideCountが1以下だった場合
            if(slideCount < 1){
                //スライド1枚目から6枚目に移動(アニメーション0秒)
                slideCount = 5;
                delayedCall(0.5,function(){
                    $(".slideSet").animate({
                        left: slideCount * -slideWidth
                    },0);
                });

                //slideCountがslideNum上限に差し掛かった場合
                }else if(slideCount == slideNum){
                    slideCount = 0;
                    delayedCall(0.5,function(){
                        $(".slideSet").animate({
                            left: slideCount * -slideWidth
                        },0);
                    });
                }
        //遅延時間の設定
        function delayedCall(second, callBack){
            setTimeout(callBack, second * 1000);
        }
    }