Vue.jsで画像をtransitionさせてはいけない

Vue.jsの[transition](https://jp.vuejs.org/v2/guide/transitions.html)を使うと、フェードイン・フェードアウトを伴うスライドショーを簡単に作れます。

```html
<div id="app">
  <transition name="fade">
    <img :src="images[index]" :key="images[index]" width="320" height="213" class="slideshow">
  </transition>
</div>
```

```js
new Vue({
  el: "#app",
  data: {
  	index: 0,
    images: [
      'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Lagopus_muta_japonica_Mount_Tsubakuro.jpg/320px-Lagopus_muta_japonica_Mount_Tsubakuro.jpg',
      'https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Pair_of_mandarin_ducks.jpg/320px-Pair_of_mandarin_ducks.jpg',
      'https://upload.wikimedia.org/wikipedia/commons/thumb/7/7c/Vicu%C3%B1a_-_Chimborazo%2C_Ecuador.jpg/320px-Vicu%C3%B1a_-_Chimborazo%2C_Ecuador.jpg',
    ]
  },
  mounted() {
  	setInterval(() => {
    	this.index = this.index < this.images.length - 1 ? this.index + 1 : 0;
    }, 3000);
  },
})
```

```css
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
.slideshow {
  position: absolute;
}
```

このコードは https://jsfiddle.net/76xkzqmg/1/ で動作確認できます。

img要素のsrcを動的に書き換えて、transitionさせているので、一見シンプルで良い実装に見えます。しかし、このコードには大きな問題があります！

ChromeのDevToolsを開いて、Networkタブを見てみましょう。すると、画像が切り替わるたびに、毎回画像がダウンロードされていることがわかります。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33450/153fedf7-81ef-626c-4043-57addf5de4be.png)

もし、スライドショーに使う画像が1枚1MBで、5秒ごとに切り替わるとすると、1分で12MB、1時間で720MBをダウンロードすることになります。

また、このダウンロードは、PC版のChromeの場合、タブがアクティブでないときでも発生します。従量課金制の回線を使っている際に、そんなページを開きっぱなしにしていたら、、、:scream: 

ということで、無限ダウンロードが発生しないよう、対策を考えます。

## transitionの仕組みとimg要素

Vue.jsのtransitionは、要素の追加・削除にフックしてアニメーションを行います。そのため、単にsrc属性を書き換えるだけでなく、img要素自体をDOMから消して、新しく追加しなければなりません。このために、サンプルコードでは `key` 属性を利用しています。

一方、img要素は新しく追加されると、src属性に指定されたURLに画像を取りに行きます。

この2つの機能が組み合わさることで、無限ダウンロードが発生しています。

transitionを使わず、実装する方針を考えた方がよさそうです。

## CSSアニメーションによるスライドショー

Vueのtransitionを使わなくても、JavaScriptとCSSアニメーションの組み合わせで、スライドショーは実現できます。

```html
<div id="app">
  <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Lagopus_muta_japonica_Mount_Tsubakuro.jpg/320px-Lagopus_muta_japonica_Mount_Tsubakuro.jpg" class="slideshow">
  <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Pair_of_mandarin_ducks.jpg/320px-Pair_of_mandarin_ducks.jpg" class="slideshow fadeout">
  <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/7c/Vicu%C3%B1a_-_Chimborazo%2C_Ecuador.jpg/320px-Vicu%C3%B1a_-_Chimborazo%2C_Ecuador.jpg" class="slideshow fadeout">
</div>
```

```js
new Vue({
  el: "#app",
  data: {
  	index: 0,
  },
  mounted() {
    const images = document.getElementsByClassName('slideshow');
    this.slideshow(images);
    setInterval(() => {
      this.index = this.index < images.length - 1 ? this.index + 1 : 0;
      this.slideshow(images);
    }, 3000);
  },
  methods: {
  	slideshow(images) {
      const current = images[this.index];
      const prev = images[this.index - 1] ? images[this.index - 1] : images[images.length - 1];
      current.classList.add('fadein');
      current.classList.remove('fadeout');
      prev.classList.remove('fadein');
      prev.classList.add('fadeout');
    }
  }
})
```

```css
.fadein {
  opacity: 1;
  transition: opacity 0.5s;
}

.fadeout {
  opacity: 0;
  transition: opacity 0.5s;
}

.slideshow {
  position: absolute;
}
```

動作確認は https://jsfiddle.net/tjn4h6yz/ で。

slideshowメソッドで、新しく表示する要素(current)にfadeinクラスを付け、今まで表示していた要素(prev)にはfadeoutクラスを付けています。これによって、Vue.jsのtransitionと同様にアニメーションを付けることができます。

また、この実装の場合、img要素の追加や削除は行っていないので、無限に画像がダウンロードされるような不都合は発生しません。