Vue.jsのtransitionでクロスフェードなスライドショーを実現する。

# はじめに
jQueryのSlideJSなどでクロスフェードなスライドカルーセルを実現したことはあったのですが、それをVue.jsでもやってみました。

なお、以下の通りです。
* Vue.jsの使い方には言及しません。
* transitionの詳しい使い方は、https://jp.vuejs.org/v2/guide/transitions.html を参照ください。

# 概要
* Vueでコンポーネント化しています。
* Vue.jsのtransitionタグを使います。
* Vueの環境はvue-cli3を使って作っています。

# 環境的なもの
* vue --version 3.5.1
* node --version v10.8.0
* VueをTypeScriptで記述
* HTMLはpugで記述
* cssはstylusで記述

# 全体像
何はともあれ、まず全体像。

``` vue:sample.vue
<template lang="pug">
div
  .slide-outer
      transition(name="fade")
        .slide-inner(v-for="(slide, idx) in slides" v-if="currentSlide == idx" :key="idx")
          img.slide-img(:src="slides[idx].img")
</template>

<script lang="ts">
import { Component, Watch, Vue } from 'vue-property-decorator'

@Component({
})

export default class SlideShow extends Vue {
  private currentSlide: number = 0
  private slides: any = [
    { img: require('@/assets/home_slide_1.jpg') },
    { img: require('@/assets/home_slide_2.jpg') },
    { img: require('@/assets/home_slide_3.jpg') },
    { img: require('@/assets/home_slide_4.jpg') }
  ]
  private fade: string = 'next'
  private show: boolean = true
  private timer: number = 0

  created () {
    this.$nextTick(() => {
      this.timer = setInterval(() => {
        this.autoPlay()
      }, 3000)
    })
  }

  autoPlay () {
    this.currentSlide++
    if (this.currentSlide === this.slides.length) {
      this.currentSlide = 0
    }
  }
}
</script>
<style lang="stylus">
.slide-outer
  position relative
  overflow hidden
  width 100vw
  height 300px
  display flex

.slider-inner
  position absolute
  width 100vw
  height 300px

.slide-img
  width 100vw
  height 300px
  object-fit cover

.fade-enter-active
  transition all 1s ease

.fade-leave-active
  transition all 1s ease
  position absolute

.fade-enter
.fade-leave-to
  opacity 0
</style>
```

# HTML部分(\<template\>タグ内)
```vue
<template lang="pug">
div
  .slide-outer
      transition(name="fade")
        .slide-inner(v-for="(slide, idx) in slides" v-if="currentSlide == idx" :key="idx")
          img.slide-img(:src="slides[idx].img")
</template>
```

* 後で\<script\>内を見ればわかりますが、

```vue
private slides: any = [
  { img: require('@/assets/home_slide_1.jpg') },
  { img: require('@/assets/home_slide_2.jpg') },
  { img: require('@/assets/home_slide_3.jpg') },
  { img: require('@/assets/home_slide_4.jpg') }
]
```
という形でスライドショーにしたい画像のリストを作っています。そして、`v-for`を使って回しています。

* 画像自体は、assetsディレクトリ配下に配置し、`home_slide_[idx].jpg`という名前にしています。
* `v-for`で回し、要素のインデックス番号が、`imgタグ`で`v-bind`してある`src`部分の`[idx]`内に順々に入っていき、画像が入れ替わっていくようになリます。
* `.slide_inner`に`v-if`を使い、currentSlideとインデックスが同じ番号の画像を表示させるようにしています。

# \<script\>部分

``` vue
<script lang="ts">
import { Component, Watch, Vue } from 'vue-property-decorator'

@Component({
})

export default class SlideShow extends Vue {
  private currentSlide: number = 0
  private slides: any = [
    { img: require('@/assets/home_slide_1.jpg') },
    { img: require('@/assets/home_slide_2.jpg') },
    { img: require('@/assets/home_slide_3.jpg') },
    { img: require('@/assets/home_slide_4.jpg') }
  ]
  private fade: string = 'next'
  private show: boolean = true
  private timer: number = 0

  created () {
    this.$nextTick(() => {
      this.timer = setInterval(() => {
        this.autoPlay()
      }, 3000)
    })
  }

  autoPlay () {
    this.currentSlide++
    if (this.currentSlide === this.slides.length) {
      this.currentSlide = 0
    }
  }
}
</script>
```

* createdした時に`setInterval`で`autoPlay()`が発火するようにしています。
* `autoPlay()`のメソッド内で、`currentSlide`の値をインクリメントしていき、`slides`のリストの長さと同じになった時、インデックスの値を`0`にするようにしています。


# \<style\>内(css)

```vue
<style lang="stylus">
.slide-outer
  position relative
  overflow hidden
  width 100vw
  height 300px
  display flex

.slider-inner
  position absolute
  width 100vw
  height 300px

.slide-img
  width 100vw
  height 300px
  object-fit cover

.fade-enter-active
  transition all 1s ease

.fade-leave-active
  transition all 1s ease
  position absolute

.fade-enter
.fade-leave-to
  opacity 0
</style>
```
* ブラウザ幅いっぱいの画像にしたかったため、widthには`vw`を使っています。画像の幅や大きさは好みに合わせて調整してください。
* Vue.jsで`<transition>タグ`を使うと、`[transitionタグのname属性で指定した名前]-enter`や`[transitionタグのname属性で指定した名前]-leave`などがクラス名で使えるようになります。`○○-enter-active`や、`○○-leave-active`で、transitionの動きを決めれるようになります。
* `.fade-leave-active`に`positinon absolute`をしておかないと、クロスフェードがうまく効きません。フェードアウトはうまくいってもフェードインが「パチっ」と画像は挿入される感じなってしまいます(ここで若干ハマりました...)。

# デモ
ということで、デモです。
大きさ(横幅など)はコンバートしているので、小さくなってます。
こんな感じで動くというのがわかればと思います。
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/00111e78-0a51-da4f-bd7f-62edd9e25ee0.gif)



# その他補足

* アニメーションの切り替わりの部分は、cssのtransitionプロパティの時間をうまく調整してみてください。
* 画像自体の入れ替えまでの時間は、scriptのsetTimeoutの時間で調整してみてください。


以上。

※ 参考にさせていただいた記事
https://qiita.com/ktn/items/65a0e3112246f86d04de