If you do not intend to crossfade the images (i.e. only one image is actually on the screen at any given point of time), you can restructure your code such as only a single <Slide> component is rendered at a single time. You simply assign a unique key that is dynamically updated, e.g. your active data of the slider component.
Your template will now look like this:
<div class="carousel-wrapper" id="promotions">
<transition name="component-fade" mode="out-in">
<!-- Bind image url dynamically, bind key to active index -->
<Slide class="slide" :url="imageUrl" :key="active"></Slide>
</transition>
</div>
The imageUrl is simply a computed property that returns the image URL by index:
imageUrl() {
return this.list[this.active].url;
}
The Slide component should be as "dumb" as we need it to: in this case, it simply receives the dynamically-bound image URL and nothing more:
<div>
<img :src="url" class="image">
</div>
Note:
- you should use a transition duration that is equal to, or shorter than, the time interval of your
active property updates. In your original code the slides are cycled ever 3s but the transition-duration is set to 4s, leading to really janky transitions
- the solution will be more complicated if you want a crossfade animation.
Here is a proof-of-concept example:
Vue.component('Slide', {
template: `
<div>
<img :src="url" class="image">
</div>`,
props: ['url']
});
Vue.component('component-2', {
template: `<div>
<p>component 2</p>
</div>`
});
new Vue({
el: '#example',
template: `
<div class="carousel-wrapper" id="promotions">
<transition name="component-fade" mode="out-in">
<Slide class="slide" :url="imageUrl" :key="active">
</Slide>
</transition>
</div>`,
data() {
return {
list: [{
url: 'https://static.pexels.com/photos/572741/pexels-photo-572741.jpeg'
},
{
url: 'https://static.pexels.com/photos/575739/pexels-photo-575739.jpeg'
},
{
url: 'https://static.pexels.com/photos/576832/pexels-photo-576832.jpeg'
}
],
active: 0
}
},
computed: {
numberOfSlides() {
return this.list.length - 1
},
imageUrl() {
return this.list[this.active].url;
}
},
created() {
this.rotate()
},
methods: {
moveSlide(event) {
this.active = event.srcElement.dataset.slide
this.list.forEach(el => {
el.active = false
})
this.list[event.srcElement.dataset.slide].active = true
},
rotate() {
if (this.active < this.numberOfSlides) {
this.active = this.active + 1
} else {
this.active = 0
}
let _this = this
setTimeout(_this.rotate, 3000)
}
}
});
.carousel-wrapper {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
}
.carousel {}
.dots {
max-width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
position: absolute;
bottom: 2em;
z-index: 1;
}
.dot {
height: 1em;
width: 1em;
margin: 0 1em 0 1em;
border-radius: 50%;
background-color: red;
}
.component-fade-enter-active,
.component-fade-leave-active {
transition: opacity 1s ease;
}
.component-fade-enter,
.component-fade-leave-to {
opacity: 0;
}
.slide {
width: 100%;
height: 650px;
}
.image {
max-width: 100%;
max-height: 650px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="example">
</div>