Vue.jsのtransitionを使うと、フェードイン・フェードアウトを伴うスライドショーを簡単に作れます。
<div id="app">
<transition name="fade">
<img :src="images[index]" :key="images[index]" width="320" height="213" class="slideshow">
</transition>
</div>
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);
},
})
.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タブを見てみましょう。すると、画像が切り替わるたびに、毎回画像がダウンロードされていることがわかります。。
もし、スライドショーに使う画像が1枚1MBで、5秒ごとに切り替わるとすると、1分で12MB、1時間で720MBをダウンロードすることになります。
また、このダウンロードは、PC版のChromeの場合、タブがアクティブでないときでも発生します。従量課金制の回線を使っている際に、そんなページを開きっぱなしにしていたら、、、
ということで、無限ダウンロードが発生しないよう、対策を考えます。
transitionの仕組みとimg要素
Vue.jsのtransitionは、要素の追加・削除にフックしてアニメーションを行います。そのため、単にsrc属性を書き換えるだけでなく、img要素自体をDOMから消して、新しく追加しなければなりません。このために、サンプルコードでは key
属性を利用しています。
一方、img要素は新しく追加されると、src属性に指定されたURLに画像を取りに行きます。
この2つの機能が組み合わさることで、無限ダウンロードが発生しています。
transitionを使わず、実装する方針を考えた方がよさそうです。
CSSアニメーションによるスライドショー
Vueのtransitionを使わなくても、JavaScriptとCSSアニメーションの組み合わせで、スライドショーは実現できます。
<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>
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');
}
}
})
.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要素の追加や削除は行っていないので、無限に画像がダウンロードされるような不都合は発生しません。