I’m working on a Vue.js component where I’m creating a flip animation with images that change every few seconds. The goal is to randomly change the image, but there is an issue during the transition.
What I want:
What happens:
The logic has one flaw. The animation between image 1 and image 2 works as desired. But in the animation from image 2 to image 3, image 1 reappears for a short moment. (And this happens in all succeeding animations, too.)
You can find a screen recording of this issue here.
Code:
Template:
<template>
<div class="start-screen">
<div class="container">
<div id="card">
<div
v-for="(image, index) in images"
:key="index"
:class="['flip-page', { active: index === currentIndex, turnedLeft: index === previousIndex }]"
:style="{ backgroundImage: `url(${image})` }"
></div>
</div>
</div>
</div>
</template>
Script:
<script>
export default {
name: "StartScreen",
data() {
return {
images: [],
currentIndex: 0,
previousIndex: null, // Track the last image for smooth transition
};
},
methods: {
loadImages() {
const context = require.context("../assets/icons", false, /\.(png|jpe?g|svg)$/);
this.images = context.keys().map(context);
},
getRandomIndex() {
if (this.images.length <= 1) return 0;
let newIndex;
do {
newIndex = Math.floor(Math.random() * this.images.length);
} while (newIndex === this.currentIndex); // Avoid immediate repetition
return newIndex;
},
nextSlide() {
this.previousIndex = this.currentIndex; // Store the previous image index
this.currentIndex = this.getRandomIndex(); // Get a new random image
},
},
mounted() {
this.loadImages();
setInterval(this.nextSlide, 6000); // Flip every 6 seconds
},
};
</script>
Style:
<style scoped>
.start-screen {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* Container for the flip effect */
.container {
width: 260px;
height: 260px;
position: relative;
perspective: 800px;
}
/* Stacked images */
#card {
width: 100%;
height: 100%;
position: absolute;
}
/* Flip animation for each page */
.flip-page {
width: 100%;
height: 100%;
position: absolute;
background-size: cover;
background-position: center;
border-radius: 10px;
backface-visibility: hidden;
transition: transform 3s;
transform: rotateY(180deg);
}
/* Flip the active page forward */
.active {
transform: rotateY(0deg);
z-index: 2;
}
/* Only show the most recent previous page */
.turnedLeft {
transform: rotateY(-180deg);
z-index: 1;
}
</style>
Question: Can anyone explain why this happens and how I can fix it to ensure only the current and next images are visible and there is no flickering of a third image? Thank you very much!
Just hide the .flip-page
elements that are neither .active
nor .turnedLeft
, like this:
.flip-page:not(.active):not(.turnedLeft) {
opacity: 0;
}
:not
CSS pseudo-class - MDN DocsOr simply put, the same:
.flip-page:not(.active, .turnedLeft) {
opacity: 0;
}
const { createApp, ref, onMounted } = Vue;
createApp({
setup() {
const images = ref([
"https://picsum.photos/260/260?1",
"https://picsum.photos/260/260?2",
"https://picsum.photos/260/260?3",
"https://picsum.photos/260/260?4",
]); // Example images listed instead of loading for CDN Example
const currentIndex = ref(0);
const previousIndex = ref(null); // Track the last image for smooth transition
const getRandomIndex = () => {
if (images.value.length <= 1) return 0;
let newIndex;
do {
newIndex = Math.floor(Math.random() * images.value.length);
} while (newIndex === currentIndex.value); // Avoid immediate repetition
return newIndex;
};
const nextSlide = () => {
previousIndex.value = currentIndex.value; // Store the previous image index
currentIndex.value = getRandomIndex(); // Get a new random image
};
onMounted(() => {
setInterval(nextSlide, 3000); // Flip every 6 seconds (changed to 3s for test)
});
return { images, currentIndex, previousIndex };
}
}).mount("#app");
.start-screen {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* Container for the flip effect */
.container {
width: 260px;
height: 260px;
position: relative;
perspective: 800px;
}
/* Stacked images */
#card {
width: 100%;
height: 100%;
position: absolute;
}
/* Flip animation for each page */
.flip-page {
width: 100%;
height: 100%;
position: absolute;
background-size: cover;
background-position: center;
border-radius: 10px;
backface-visibility: hidden;
transition: transform 3s;
transform: rotateY(180deg);
}
.flip-page:not(.active, .turnedLeft) {
opacity: 0;
}
/* Flip the active page forward */
.active {
transform: rotateY(0deg);
z-index: 2;
}
/* Only show the most recent previous page */
.turnedLeft {
transform: rotateY(-180deg);
z-index: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.4/vue.global.prod.js"></script>
<div id="app">
<div class="start-screen">
<div class="container">
<div id="card">
<div
v-for="(image, index) in images"
:key="index"
:class="['flip-page', { active: index === currentIndex, turnedLeft: index === previousIndex }]"
:style="{ backgroundImage: `url(${image})` }"
></div>
</div>
</div>
</div>
</div>