この記事で扱っている内容
・v-on、v-bind、v-show
・トランジション
・簡単なsassによるレイアウト
・ setIntervalとthis
対象読者様
・プラグインを使わないでカルーセルを練習がてら実装したい方
・Vueの基礎構文を確認したい方
・ハンズオンで練習したい方
※サンプル画像は好きなものをimagesフォルダに入れてください。
完成品と機能
・メイン画像が上に大きめにある
・左右ボタンをクリックすると画像が切り替わる
・下のサムネイルをクリックすると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>
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;//左右中央揃えに
}
終わりに
お疲れ様でした。基礎の基礎のお話でした。