【Vue基礎練習】ハンズオンでVueの基礎構文を確認・解説【カルーセル作成】

## この記事で扱っている内容
・v-on、v-bind、v-show
・トランジション
・簡単なsassによるレイアウト
・ setIntervalとthis

## 対象読者様
・プラグインを使わないでカルーセルを練習がてら実装したい方
・Vueの基礎構文を確認したい方
・ハンズオンで練習したい方
※サンプル画像は好きなものをimagesフォルダに入れてください。

### 完成品と機能

![スクリーンショット 2022-10-10 6.08.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420377/a827f0a5-4742-aa64-26a7-0b05407db229.png)

・メイン画像が上に大きめにある
・左右ボタンをクリックすると画像が切り替わる
・下のサムネイルをクリックするとopacityが変わる(クラスのつけ外し)
・メイン画像は下のサムネイルと同じである
・start stopボタンを押すとカルーセルがスタート、ストップ

## htmlを書いていく

構造はcarouselクラスの中に
・BEMでcarousel & __main(メイン画像)、__prev、__next、__thumbnails、とbtn-boxがある状態。

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel ="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
    <link rel="stylesheet" href="app.css">
</head>
<body>
    <div id="app">
        <section class="carousel">
            <div class="carousel__main"></div>
            <div class="carousel__prev">

            </div>
            <div class="carousel__next">

            </div>
            <ul class="carousel__thumbnails">
                
            </ul>
            <div class="btn-box">
                <button class="btn">Start</button>
                <button class="btn">Stop</button>
            </div>
        </section>
    </div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
<script src="main.js"></script>
</body>
</html>

```

これで骨格は完成したのでprevとnextの中に矢印アイコンをコピペしてください。

```html
フォントオーサムをhead内で読み込み。
<link rel ="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

左　
<i class="fas fa-angle-left fa-5x"></i>
右
<i class="fas fa-angle-right fa-5x"></i>

```
現状
![スクリーンショット 2022-10-10 6.35.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420377/226d68d4-1019-1f56-7d66-674179a28797.png)

### main.jsの記述

```javascript
let app = new Vue({
    el: '#app',
    data() {
        return {
            exactActive: 0, //サムネイルのidを監視するためのデータ (0〜6の範囲の値)
            intervalId: null, //スタートストップ処理のためにsetIntervalでのちに使う
            isPlaying: false, //上記と同様、後でセットで使います
            images: [
                {id:0, img:'images/image000.jpg'},
                {id:1, img:'images/image001.jpg'},
                {id:2, img:'images/image002.jpg'},
                {id:3, img:'images/image003.jpg'},
                {id:4, img:'images/image004.jpg'},
                {id:5, img:'images/image005.jpg'},
                {id:6, img:'images/image006.jpg'},
            ]
        }
    },

```

 ### 画像が揃ったのでhtmlを編集
```html
変更箇所　v-on(@click)を矢印に追加
    <div class="carousel__prev" @click="prev">
        <i class="fas fa-angle-left fa-5x"></i>
    </div>
    <div class="carousel__next" @click="next">
        <i class="fas fa-angle-right fa-5x"></i>
    </div>

```

### ボタン機能実装
--クリックしたらitemのidが変わる。
```javascript

        prev(){
            if(this.exactActive <= 0) { //要素数より少なくなったら
                this.exactActive = this.images.length - 1 //配列の要素数より1つ少ない値を代入。 
//「-1」をつける。これはthis.exactActiveに-1が代入されるのを防ぐため。-1が代入されると後に設定するitem.idには−１がないためエラーとなる。
            } else {
                this.exactActive--
            }
        },
        next(){
//prevと同様にthis.exactActiveに7が代入される前にthis.exactActiveを0に差し戻す。
            if(this.exactActive >= this.images.length - 1) {
                this.exactActive = 0
            } else {
                this.exactActive++
            }
        },

```

### htmlのテンプレート構文記述開始

```html
ul内にliを追加して属性をつけていく
<ul class="carousel__thumbnails">
    <li v-for="(image,id) in images"  サムネイルはリストでv-forで反復して表示する、引数はimageとid
    :key="image.id"   keyは必須でimage.idと紐付ける
    :class="{current: exactActive === image.id}"　　動的にcurrentクラス（opacity制御クラス）をつけ外しする。　「もし、exactActiveがimage.idと同じならcurrentクラスをつける」
    @click="clickCurrent(image.id)">　実際に動作するのはココでサムネイルのliをクリックするとclickCurrentメソッドが呼ばれる。
    <img :src="image.img">
    </li>
</ul>

```

### clickCurrentの実装

```javascript
        clickCurrent(id) { //引数にはクリックしたimage.idが入ってくるのでexactActiveにその値が代入される。
//exactActiveの値が変われば動的に:class="条件式"が変わりcurrentクラスが付与されて色が変わる。
            this.exactActive = id
        },

```

### main画像のhtmlの記述

```html
<transition name="active">
    <div
        v-show="exactActive === images[exactActive].id">
        <img :src="images[exactActive].img" alt="">
    </div>
</transition>

```
transitionタグを使うと自動で、name="〇〇"の箇所に対応する下記のクラス名が生成されて、トランジションを実行する。
1.〇〇-enter-to
2.〇〇-enter
3.〇〇-enter-active
4.〇〇-leave-to
5.〇〇-leave
6.〇〇-leave-active

これはsassで簡潔にかけるのでその箇所を貼り付けます。

```css
.active {
  &-enter{
    opacity: 0;
    &-to{
      opacity:1;
    }
    &-active{
      transition: opacity .5s;
    }
  }
  &-leave{
    opacity:1;
    &-to{ 
      opacity:0;
    }
    &-active{
      transition: opacity .5s;
    }
  } 
}

```

### main画像のdivの記述解説
```html
<transition name="active">
    <div
        v-show="exactActive === images[exactActive].id">
        <img :src="images[exactActive].img" alt="">
    </div>
</transition>
```

v-show(表示するかどうかの条件)の条件式と動的に画像のsrcを切り替えている。
まず、exactActiveは「prev」 「next」 「li」のどれかを押すと変わりますが、それが右辺と同じだったら表示する。という意味でこれは当たり前のようにtrueになって表示されます。
「mainの画像は1つだけでそれ以外はこの条件式は通しませんよ！」っていうくらいの感じです。
その上で:srcをimagesのexactActive番目のimgに設定しています。

### setIntervalの内容

```html
    <div class="btn-box">
        <button class="btn" @click="playCarousel">Play</button>
        <button class="btn" @click="stopCarousel">Stop</button>
    </div>
```

### Jsの記述

```javascript
--略--

data() {
return {
    intervalId: null,
    isPlaying: false,
    }
}
↑↑↑↑↑追記↑↑↑↑↑

--略--
playCarousel() {
    this.isPlaying = true
        let _this = this　//setIntervalはwindowオブジェクトのメソッドなので一度thisを別の変数に代入しています(中でthis)は使えません。
        this.intervalId = setInterval(() => {
        _this.next()
        }, 2000)
    },
stopCarousel() {
    this.isPlaying = false
    clearInterval(this.intervalId)
}

```

isPlayingでストップスタートの切り替えをおこなっています。

最後に全てのコードを貼っておきます

### html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel ="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
    <link rel="stylesheet" href="app.css">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <section class="carousel">
            <div class="carousel__main">
                <transition name="active">
                    <div
                        v-show="exactActive === images[exactActive].id">
                        <img :src="images[exactActive].img" alt="">
                    </div>
                </transition>
            </div>
            <div class="carousel__prev" @click="prev">
                <i class="fas fa-angle-left fa-5x"></i>
            </div>
            <div class="carousel__next" @click="next">
                <i class="fas fa-angle-right fa-5x"></i>
            </div>
            <ul class="carousel__thumbnails">
                <li v-for="(image,id) in images" 
                :key="image.id" 
                :class="{current: exactActive === image.id}"
                @click="clickCurrent(image.id)">
                <img :src="image.img">
            </li>
        </ul>
        <div class="btn-box">
            <button class="btn" @click="playCarousel">Play</button>
            <button class="btn" @click="stopCarousel">Stop</button>
        </div>
        </section>
    </div>
    <script src="main.js"></script>
</body>
</html>

```

### main.js
```javascript
let app = new Vue({
    el: '#app',
    data() {
        return {
            exactActive: 0,
            intervalId: null,
            isPlaying: false,
            images: [
                {id:0, img:'images/image000.jpg'},
                {id:1, img:'images/image001.jpg'},
                {id:2, img:'images/image002.jpg'},
                {id:3, img:'images/image003.jpg'},
                {id:4, img:'images/image004.jpg'},
                {id:5, img:'images/image005.jpg'},
                {id:6, img:'images/image006.jpg'},
            ]
        }
    },
    methods: {
        clickCurrent(id) {
            this.exactActive = id
        },
        prev(){
            if(this.exactActive <= 0) {
                this.exactActive = this.images.length - 1
            } else {
                this.exactActive--
            }
        },
        next(){
            if(this.exactActive >= this.images.length - 1) {
                this.exactActive = 0
            } else {
                this.exactActive++
            }
        },
        playCarousel() {
            this.isPlaying = true
                let _this = this
                this.intervalId = setInterval(() => {
                _this.next()
                }, 2000)
        },
        stopCarousel() {
            this.isPlaying = false
            clearInterval(this.intervalId)
        }
    },

})
```

### scss

```scss
.carousel{
  width: 840px;
  height: 400px;
  margin: 0 auto;
  position: relative;
  &__main{
    width: 640px;
    height: 400px;
    margin:0 auto;
  }
  &__prev{
    position: absolute;
    top: 150px;
    left:20px;
    cursor: pointer;
    color: lightgray;
    &:hover{
      transition-duration: .2s;
      transform: translateX(-10px);
    }
  }
  &__next{
    position: absolute;
    top: 150px;
    right:20px;
    cursor: pointer;
    color:lightgray;
    &:hover{
      transition-duration: .2s;
      transform: translateX(10px);
    }
  }
  &__thumbnails{
    display: flex;
    justify-content: center;
    list-style: none;
    padding: 0;
    & li{
      cursor: pointer;
      opacity: 0.4;
      &:hover{
        opacity: 1;
      }
      &.current{
        opacity: 1;
      }
    }
    & img{
      width: 80px;
    }
  }
}

.active {
  &-enter{
    opacity: 0;
    &-to{
      opacity:1;
    }
    &-active{
      transition: opacity .5s;
    }
  }
  &-leave{
    opacity:1;
    &-to{ 
      opacity:0;
    }
    &-active{
      transition: opacity .5s;
    }
  } 
}
.btn {
  /* ブラウザ特有のスタイルを無効に */
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;

  margin: 10px;
  padding: 0.6em 1em; /* 塗りの余白 */
  font-size: 1em; /* フォントサイズ */
  background-color: #1aa1ff; /* 背景色 */
  color: #FFF; /* テキストカラー */
  cursor: pointer; /* カーソルを指マークに */
  border-radius: 3px; /* 角の丸み */
  border: 0; /* 枠線を消す */
  transition: 0.3s; /* ホバーの変化を滑らかに */
}
.btn:hover {
  background-color: #064fda;
}

.btn-box {
  display: flex;//横並びに
  justify-content: center;//左右中央揃えに
}

```

## 終わりに

お疲れ様でした。基礎の基礎のお話でした。