お母さんでもわかるカルーセルスライダー

# カルーセルスライダーの実装　〜スライド編〜
## 目的
改めて作成の流れを整理したいと思いこちらの記事を作成いたしました。
クオリティは低いですが、最後まで見てくださると嬉しいです。

## 作成までの流れ
[まず完成形を確認する！](#まず完成形を確認する)
[スライドを実装していく！](#スライドを実装していく)
[インジケーターを実装していく！](#インジケーターを実装していく)
[前後に進む矢印を実装していく！](#前後に進む矢印を実装していく)
[全コード](#全コード)


## まず完成形を確認する！
![demo](https://gyazo.com/34586a3a228bec1280bab7bb6121227a/raw)
それではまず完成形の確認です！

要件は下記の通りです
・1000ms ごとに自動で右の画像にスライドする
・最後のスライドの後に右にスライドした時、最初のスライドが表示される
・矢印を押すと画像が矢印の方向にスライドする
・現在表示されてる画像のインジケーターの色が白になる
・インジケーターを押したら指定のスライドが表示される

### 1. 機能を洗い出す
まずはどんな機能が備わっているか改めて言葉にします！
A. 自動で画像が左にスライドしている
B. １秒ごとにスライドしている
C. サイドの画像が表示されたら１枚目の画像に戻る
D. 表示されている画像の丸ぽちが白くなる
E. 下の丸ぽちを押したらその画像が表示される
F. 矢印を押すと進む/戻る

### 2. 機能をグループ化
機能を洗い出したら今度はグループ化しましょう！

・画像のスライド
A、B、C

・丸ぽち
D、E

・矢印
F

このように3つのグループに分けることができました。

### 3. 実装順番を決める
上記で3つに分けることができましたが、次は何から手をつけていくか決めます。
「丸ぽち」と「矢印」については画像がスライドしていないと確認のしようがないので、まずは
・A、B、C

そして矢印を実装させるときは「丸ぽち」が動いているかも確認をしたいので２番目に
・D、E

最後に
・F

という順番で実装したいと思います。

## スライドを実装していく！
### 1. 画像をスライドさせる
<img width="600" alt="slider.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/746d175b-d4ae-d62a-6245-843ef82b76bb.png">

まず上記のように大枠を作り、

<img width="600" alt="slider.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/2e3259b3-6913-e995-d223-6d3b2ecd1ac5.png">

画像（absolute,flex等を使いながら）をはめていきます。
白い部分は親要素に設定したborderです！
**(スライドがわかりやすいようにoverflow: hiddenは後で使用)**

では「画像一枚分」を左に動かしたいので、left：-100%　を指定してあげれば良さそうです！
※なぜ　100%　で画像一枚分をスライドできるのかは、[こちらの記事](https://developer.mozilla.org/ja/docs/Web/CSS/Containing_block)を確認してください。



スライドさせるためのコードを書いていきます！
```index.js
const duration = 1000;
let currentIndex = 0;

function changeSlide() {
  $slides.animate({                   //$slidesにアニメーションを与える
    left: (currentIndex) * -100 + '%' //leftに-100%を与える
  }, duration);                       //１秒かけて
}

function goNextSlide() {
  currentIndex++;                     //変数currentIndexを１増やす
  changeSlide();                      //changeSlide()を実行する
}

function startTimer() {
  timer = setInterval(goNextSlide, duration);　　//１秒ごとにstartTimer()を実行する
}

startTimer();
```
**changeSlide関数**(スライドさせる大元の関数で、「１秒かけて、leftに-100%を与える」という意味)
・$slides(画像をまとめている要素)にanimateを指定してcssを適用させる
・currentIndex → 初期値に０が設定されている（この数に応じて、left値が決まる）

**goNextSlide関数**
・「++」でcurrentIndexを１ずつ増やして、changeSlide()を実行する
※なぜ「currentIndex++」をchangeSlide()に記述しないかというと、後ほど実装する「矢印」の時にも役立つからです。

**startTimer関数**
・setIntervalを使って、goNextSlide（）を１秒ごと繰り返す

![demo](https://gyazo.com/e8ed65c848cc40779cfb57b79a246d65/raw)
しっかりとスライドされました！

### 2. スライドをループさせる
前述でスライドが実装できましたが、このままでは永遠にleftにスライドし続けてしまいます・・・。
最後の画像が表示されたら１枚目に戻したいので、if文（もし最後の画像が表示されたら〜）を書いていきたいと思います！

```index.js
const $imageNumber = $slide.length;
const duration = 1000;
let currentIndex = 0;

function changeSlide() {
     $slides.animate({
       left: (currentIndex ) * -100 + '%'
     }, duration);
     if (currentIndex == $imageNumber - 1) {
       currentIndex = 0;
      $slides.animate({
        left: (currentIndex) * -100 + '%'//この記述で１枚目に戻る
      }, 0);
   }
}
```
**changeSlide関数**
if文を追加しました。
「もしcurrentIndexが（画像枚数 - 1）と一致したら、currentIndexを0に戻してleft値を変える」
という記述です。
※なぜ「-1」をするのかというと画像枚数は１から数え始めるが、「index = currentIndex」は0からスタートするためです。

![demo](https://gyazo.com/8186e190bb78f11ce4a9949364f7920a/raw)
このように一枚目に戻すことができました！
ただ１枚目に戻るときに不自然な動きになってしまいました。
<img width="600" alt="S__565250.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/b89d9637-8c9a-1d5a-39f4-2d64fa37194e.jpeg">

上記画像のように、最初の画像が最後にもあれば滑らかにスライドしそうです！

これを実現させるために「cloneプロパティ」を使いたいと思います。
※HTMLに記述しない理由は2つあります。
・本来は記述が必要のない画像ということ
・画像が増えたときに書き換えないといけないため


それでは記述していきます！
```index.js
function cloneItem() {
      const $firstSlide = $slides.find('li:first-child');
      $firstSlide.clone().appendTo($slides);
    }

function changeSlide() {
     $slides.animate({
       left: (currentIndex ) * -100 + '%'
     }, duration);
     if (currentIndex == $imageNumber) { //１枚追加されたので「−１」を削除します
       currentIndex = 0;
      $slides.animate({
        left: (currentIndex) * -100 + '%'
      }, 0);
   }
}

//その他省略


function init() {
   cloneItem();
   startTimer();
}
init();
```
**cloneItem()**
1行目で、「画像の１枚目を取得」
2行目で、firstSlideをクローンし、slidesの最後に追加

**changeSlide()**
「$imageNumber -1 」の-1を削除。

**init()**
実行関数が増えたので、一つの関数にまとめた方が管理しやすい

![demo](https://gyazo.com/e25492fa5519e0bb113d308a72fedd09/raw)
するとどうでしょう！滑らかにスライドしました！
※最後にoverflow: hiddenを設定して見た目を調節してあげたら、次の課題に行きたいと思います！

## インジケーターを実装していく！
### 1. インジケーターの追加
内容を確認したいと思います。

・画像枚数分のインジケーターを設置
・画像が増えたら、自動でインジケーターが増える
・現在表示されてる画像のインジケーターの色が白になる
・インジケーターを押したら指定のスライドが表示される

直接HTMLにインジケーター用の要素を記述すると、画像が増えたときにインジケーターが自動で生成されないので、jQueryから追加したいと思います！

```index.js
function createIndicator() {
  for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
    $indicators.append('<li class="indicator"></li>');
  }
}

function cloneItem() {
  //省略
}
```
for文を使って「`<li class="indicator"></li>`の作成を、画像枚数と同じ回数繰り返す」という記述になります。
このように記述することで画像が突如増えても、インジケーターが勝手に追加されます。
（cssの記述は、各自事前にお願いいたします！）

### 2. インジケーターの色の変更

続いて「現在表示されてる画像のインジケーターの色が白になる」を実装したいと思います。
```index.js
function createIndicator() {
  for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
      $indicators.append('<li class="indicator"></li>');
    }
  $indicators.find('li:first-child').eq(currentIndex).addClass('is-active');//こちらを追加
}
//以下省略

function startTimer() {
}

//こちらも追加
function changeIndicator() {
　　　　　$indicators.find('li').removeClass("is-active").eq(currentIndex).addClass("is-active");
}
```
※`is-active`に白くなるcssを記述してあるため、クラス名の位置を変更するコードを書いてあげます。
**createIndicator()**
「$indicatorsの子要素である全てのliのクラス名を削除し、currentIndex番目のliにクラス名is-activeを付与する」という記述。

**changeIndicator()**
対象全ての`li`の`is-active`クラスを削除し、`eq(currentIndex)`番目の`li`に`is-active`を追加する

そしてcreateIndicator()を、
```index.js
function changeSlide() {
//省略
createIndicator();
}
```
このようにchangeSlide()の中で呼び出すことにより、画像のスライドと同じように１秒ごとに実行されます。

![demo](https://gyazo.com/d5ce923b53d2eecb95f47e624e64494d/raw)
このように、ボタンを押すと対象の画像が表示されるようになりました！

### 2. インジケーターを押したら、対象の画像が表示される
続いて「インジケーターをクリックしたら対象の画像が表示される」という機能を実装していきますが、
画像及びインジケーターは`currentIndex`をもとに何番目かを判断しています。
↓
例：`currentIndex = 0`なら０番目の画像、`currentIndex = １`なら１番目の画像

つまり、
「インジケーターをクリックしたら`currentIndex`の番号が変わる」　＝　「画像が変わる」ということなので、それを実現させるコードを記述していきます。

```index.js
function goSomewhereIndicator() {
   if (!$slides.is(':animated')) {　//もしアニメーションが実行されていなかったら
    currentIndex = $(this).index();
    changeSlide();
  }
}

function setEvent() {
   $indicators.find('.indicator').on('click', goSomewhereIndicator);
}

function init() {
   createIndicator();
   cloneItem();
   setEvent();
   startTimer();//startTimer()の呼び出し位置を変更
}

init();
```
　
**goSomewhereIndicator()**
・currentIndex = $(this).index()
→クリックした要素自体のindexを`currentIndex`に代入する
・代入された`currentIndex`に基づいてchangeSlide()を実行させる

**setEvent()**
・イベント用の発火関数

![demo](https://gyazo.com/c6edd95feb0d171e5f55a7ee20c02f61/raw)
このように、ボタンを押すと対象の画像が表示されるようになりました！

## 前後に進む矢印を実装していく！

最後に矢印（HTML&CSSは記述済みとする）を作っていきます！

どうすればスライドが前後に動くかというと、`currentIndex`が１増える or 1減ることで画像が前後に動きそうです。
よって「矢印をクリックして、`currentIndex`が１増やす or 1減らしてから、changeSlide()を実行する」というコードを記述します。

```index.js
function goNextSlide() {
  if (!$slides.is(':animated')) {//もしアニメーションが実行されていなかったら
    currentIndex++;
    changeSlide();
  }
}

function prevSlide() {
  if (!$slides.is(':animated')) {//もしアニメーションが実行されていなかったら
    currentIndex--;
    changeSlide();
  }
}

function setEvent() {
  $buttonWrap.find('.prev').on('click', prevSlide);
  $buttonWrap.find('.next').on('click', goNextSlide);
  $indicators.find('.indicator').on('click', goSomewhereIndicator);
}
```
**goNextSlide()**
もしかしたらお気づきかもしれませんが、`goNextSlide()`はすでに「スライドを実装していく！」で定義している関数です。
`currentIndex`を1増やして`changeSlide()`を発火させるのは矢印の実装でも役に立つので、そのままクリックイベントを付与しました。

**prevSlide()**
逆に一つ後ろの画像に戻すためには`currentIndex`を1減らせばいいので、ほぼ同じ形の
「矢印をクリックしたら`currentIndex`を1減らして、`changeSlide()`を発火させる」という関数を用意しました。

しかし！！
一つ後ろの画像に戻す`prevSlide()`で新たな問題が発生しました。それは一番最初の画像から一つ戻す矢印を押すと、画像が何も表示されないという現象です。
詳しくいうと、左に画像なんてないのにどんどん右にスライドしてしまっているのです。

そこで[スライドをループさせる](#2-スライドをループさせる)という項目でやった通り、最後の画像クローンを１枚目としてセットすれば解決しそうです！

<img width="600" alt="S__1032197.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/8a4ef5b2-f18c-cbbb-8bfb-8ecf87b5b0d9.jpeg">

それでは下記コードを記述していきます。
※１枚目の画像が変わるので、cssで`left:-100%`を記述しておいてください！そうすることで一番最初に、２枚目の画像（実質１枚目の画像）が表示されます。
```index.js
function cloneItem() {
      const $firstSlide = $slides.find('li:first-child');
      const $lastSlide = $slides.find('li:last-child');//追加
      $firstSlide.clone().appendTo($slides);
      $lastSlide.clone().prependTo($slides);//追加
    }

function changeSlide() {
      $slides.animate({
        left: (currentIndex + 1) * -100 + '%'//「+1」を追加
      }, duration);
      if (currentIndex == $imageNumber) {
        currentIndex = 0;
        $slides.animate({
          left: (currentIndex + 1) * -100 + '%'//「+1」を追加
        }, 0);
      } else if (currentIndex == -1) {//else ifを追加
        currentIndex = $imageNumber - 1;
        $slides.animate({
          left: (currentIndex + 1) * -100 + '%'
        }, 0);
      }
      changeIndicator();
    }
```
**cloneItem()**
「最後の画像をクローンして、一番最初に設置」させるために2行を追加で記述しました。

**changeSlide()**
こちらの関数に２点追加しました。
①まず「+1」を記述した理由は、`cloneItem()`により、２枚目の画像が実質１枚目となるためです。
②そして`else if`ですが、
「もし`currentIndex`が-1になったら、最後の画像(「$imageNumber - 1」番目の画像)を表示させる」
という意味になります。

そうすると、
![demo](https://gyazo.com/c14cbda58d3c09c4039fd0ad458ac926/raw)
１枚目の状態で前に戻る矢印を押すと最後の画像が表示されるようになり、
スライダーを完成させることができました！！！

下記のようにスライドもインジケーターも矢印もしっかり機能しています！！
![demo](https://gyazo.com/61beaf055d36960df94087c626f11e84/raw)


## 全コード
少しごちゃごちゃしてしまったので、全体コードを記述しておきます！
```index.js
$(function () {
  function slider() {
    const $slider = $('#slider');
    const $slideWrap = $slider.find('.slideWrap');
    const $slides = $slideWrap.find('.slides');
    const $slide = $slides.find('.slide');
    const $buttonWrap = $slideWrap.find('.buttonWrap');
    const $indicators = $slider.find('.indicators');
    const $imageNumber = $slide.length;
    console.log($imageNumber)
    const duration = 1000;
    let currentIndex = 0;
    let timer = '';
    //上記は詳しく記述はしておりませんので、各自マークアップごとに変更を加えてください

    function createIndicator() {
      for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
      $indicators.append('<li class="indicator"></li>');
    }
  $indicators.find('li:first-child').eq(currentIndex).addClass('is-active');
}

    function cloneItem() {
      const $firstSlide = $slides.find('li:first-child');
      const $lastSlide = $slides.find('li:last-child');
      $firstSlide.clone().appendTo($slides);
      $lastSlide.clone().prependTo($slides);
    }

    function changeSlide() {
      $slides.animate({
        left: (currentIndex + 1) * -100 + '%'
      }, duration);
      if (currentIndex == $imageNumber) {
        currentIndex = 0;
        $slides.animate({
          left: (currentIndex + 1) * -100 + '%'
        }, 0);
      } else if (currentIndex == -1) {
        currentIndex = $imageNumber - 1;
        $slides.animate({
          left: (currentIndex + 1) * -100 + '%'
        }, 0);
      }
      changeIndicator();
    }

    function goNextSlide() {
      if (!$slides.is(':animated')) {
        currentIndex++;
        changeSlide();
      }
    }

    function prevSlide() {
      if (!$slides.is(':animated')) {
        currentIndex--;
        changeSlide();
      }
    }

    function startTimer() {
      timer = setInterval(goNextSlide, duration);　　
    }

    function changeIndicator() {
    　　　　　$indicators.find('li').removeClass("is-active").eq(currentIndex).addClass("is-active");
    }

    function goSomewhereIndicator() {
      if (!$slides.is(':animated')) {　
        currentIndex = $(this).index();
        changeSlide();
      }
    }

    function setEvent() {
      $indicators.find('.indicator').on('click', goSomewhereIndicator);
      $buttonWrap.find('.prev').on('click', prevSlide);
      $buttonWrap.find('.next').on('click', goNextSlide);
    }

   function init() {
     createIndicator();
     cloneItem();
     setEvent();
     startTimer();
   }
   init();
 }
 slider();
});
```

## まとめ
今回のスライダー作成はかなりつまづきました。各機能を実装する時はもちろんですが、個人的に一番つまづいたのは、そもそもの**どうやって・どの順番で、実装していくか**の部分です。
ですので[まず完成形を確認する！](#まず完成形を確認する)の考え方が伝わって欲しいなと思っています。

・どんな機能が備わっているのか
↓
・共通する機能は何か
↓
・何から実装していくか

ここをしっかり押さえていけば、やりたいことははっきりしていくかなと思います！

ご精読ありがとうございました！！