Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a third image briefly shown during flip animation in Vue.js?

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:

  • Possible images are loaded from a folder. We start with one image and which will be the next image is determined randomly.
  • There is a flip animation between the images.

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!

like image 723
mathbreaker Avatar asked Dec 08 '25 16:12

mathbreaker


1 Answers

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 Docs

Or 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>
like image 109
rozsazoltan Avatar answered Dec 11 '25 17:12

rozsazoltan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!