LoginSignup
0
0

More than 1 year has passed since last update.

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

Posted at

この記事で扱っている内容

・v-on、v-bind、v-show
・トランジション
・簡単なsassによるレイアウト
・ setIntervalとthis

対象読者様

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

完成品と機能

スクリーンショット 2022-10-10 6.08.59.png

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

htmlを書いていく

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

<!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の中に矢印アイコンをコピペしてください。

フォントオーサムを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

main.jsの記述

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を編集

変更箇所 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が変わる。


        prev(){
            if(this.exactActive <= 0) { //要素数より少なくなったら
                this.exactActive = this.images.length - 1 //配列の要素数より1つ少ない値を代入。 
//「-1」をつける。これはthis.exactActiveに-1が代入されるのを防ぐため。-1が代入されると後に設定するitem.idには−1がないためエラーとなる。
            } 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のテンプレート構文記述開始

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の実装

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

main画像の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で簡潔にかけるのでその箇所を貼り付けます。

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

main画像のdivの記述解説

<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の内容

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

Jsの記述

----

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

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

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

.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;//左右中央揃えに
}

終わりに

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0