Search code examples
javascriptvue.jsvuejs2carousel

Why are my navigation circles on carousel not updating when clicked?


I am building my own carousel from scratch and everything works perfectly fine except for when you click the navigation circles. If you have the interval/infinite loop prop, the circles update to the correct active slide and the same goes for pressing the side left and right buttons. However, when I click the circles directly, things get all messed up and the carousel breaks. It obviously has something to do with the switchSlide function I've written for that action, but I cannot figure it out for the life of me.

Any help or advice would be greatly appreciated!

Cheers!

Here is the Codesandbox I've made

NOTE: I just put the index inside of each circle just to maake sure the proper amount were rendering.

Carousel.vue

<template>
  <div class="audition-hero-carousel">
    <div class="carousel-inner">
      <div v-if="navigation" class="carousel-circles">
        <button
          v-for="(fileInfo, index) in modelValue"
          class="carousel-circle-item"
          :style="{ background: `${color}` }"
          :class="{ active: currentSlide === index }"
          :key="index"
          :name="index"
          @click="(index) => switchSlide(index)"
        ></button>
      </div>
      <TransitionGroup :name="transitionEffect">
        <div
          v-show="currentSlide === index"
          v-for="(fileInfo, index) in modelValue"
          :key="index"
          :name="index"
          class="carousel-item"
          @mouseenter="() => stopSlideTimer()"
          @mouseout="() => startSlideTimer()"
        >
          <img
            v-if="fileInfo.type.startsWith('image')"
            :src="fileInfo.url"
            class="_img"
            draggable="false"
          />
        </div>
      </TransitionGroup>
      <div v-if="controls">
        <button
          :style="{ color: `${color}` }"
          class="carousel-control left"
          @click="() => prev()"
        >
          ←
        </button>
        <button
          :style="{ color: `${color}` }"
          class="carousel-control right"
          @click="() => next()"
        >
          →
        </button>
      </div>
    </div>
  </div>
  <!-- {{modelValue}} -->
</template>

<script>
export default {
  name: "CAuditionHeroCarousel2",
  props: {
    /**
     * @type {{ type: 'image' | 'video' | 'youtube', url: string }[]}
     */
    modelValue: { type: Array },
    controls: {
      type: Boolean,
      default: false,
    },
    navigation: {
      type: Boolean,
      default: false,
    },
    interval: {
      type: Number,
      default: 5000,
    },
    infinite: {
      type: Boolean,
      default: true,
    },
    color: {
      type: String,
      default: "#9759ff",
    },
  },
  data() {
    return {
      currentSlide: 0,
      direction: "right",
    };
  },
  methods: {
    setCurrentSlide(index) {
      this.currentSlide = index;
    },
    prev(step = -1) {
      const index =
        this.currentSlide > 0
          ? this.currentSlide + step
          : this.modelValue.length - 1;
      this.setCurrentSlide(index);
      this.direction = "left";
      this.startSlideTimer();
    },
    _next(step = 1) {
      const index =
        this.currentSlide < this.modelValue.length - 1
          ? this.currentSlide + step
          : 0;
      this.setCurrentSlide(index);
      this.direction = "right";
    },
    next(step = 1) {
      this._next(step);
      this.startSlideTimer();
    },
    startSlideTimer() {
      if (this.infinite) {
        this.stopSlideTimer();
        setInterval(() => {
          this._next();
        }, this.interval);
      }
    },
    stopSlideTimer() {
      clearInterval(this.interval);
    },
    switchSlide(index) {
      const step = index - this.currentSlide;
      if (step > 0) {
        this.next(step);
      } else {
        this.prev(step);
      }
    },
  },
  computed: {
    transitionEffect() {
      return this.direction === "right" ? "slide-out" : "slide-in";
    },
  },
  mounted() {
    if (this.infinite) {
      this.startSlideTimer();
    }
  },
  beforeUnmount() {
    this.stopSlideTimer();
  },
};
</script>

<style lang="sass" scoped>
// Carousel Main
.audition-hero-carousel
  display: flex
  justify-content: center
  min-width: 0px
  width: 450px
  height: 200px

.carousel-inner
  position: relative
  width: 100%
  height: 100%
  overflow: hidden

.carousel-circles
  position: absolute
  display: flex
  justify-content: center
  transform: translateX(-50%)
  left: 50%
  bottom: 2em
  width: 100%
  z-index: 2

.carousel-circle-item
  width: 15px
  height: 15px
  border: none
  opacity: 0.65
  margin-right: 20px
  border-radius: 50%
  cursor: pointer
  &:hover
    cursor: pointer

  &:last-child
    margin-right: 0px

._img,
._video
  width: 100%
  height: 100%
._img
  object-fit: cover
._video > *
  height: 100%
  width: 100%

.active
  opacity: 1

/**
 Carousel Item styles
 */
.carousel-item
  position: absolute
  top: 0
  left: 0
  right: 0
  bottom: 0

.slide-in-enter-active,
.slide-in-leave-active,
.slide-out-enter-active,
.slide-out-leave-active
  transition: all 300ms ease-in-out

.slide-in-enter-from
  transform: translateX(-100%)

.slide-in-leave-to
  transform: translateX(100%)

.slide-out-enter-from
  transform: translateX(100%)

.slide-out-leave-to
  transform: translateX(-100%)

/**
 Controls
 */
.carousel-control
  outline: none
  border: none
  background: transparent
  display: inline-block
  position: absolute
  height: 50px
  width: 70px
  top: calc(50% - 20px)
  cursor: pointer

.left
  left: 0

.right
  right: 0
</style>

App.vue

<template>
  <Carousel
    :modelValue="slides"
    :interval="3000"
    :navigation="true"
    :controls="true"
    :infinite="false"
    :color="'#fff'"
  />
</template>

<script>
import Carousel from "./components/Carousel.vue";
export default {
  name: "App",
  components: {
    Carousel,
  },
  data() {
    return {
      slides: [
        { type: "image", url: "https://picsum.photos/id/1032/900/400" },
        { type: "image", url: "https://picsum.photos/id/1033/900/400" },
        { type: "image", url: "https://picsum.photos/id/1037/900/400" },
        { type: "image", url: "https://picsum.photos/id/1035/900/400" },
        { type: "image", url: "https://picsum.photos/id/1036/900/400" },
      ],
    };
  },
};
</script>

Solution

  • Try to change method call, from:

    @click="(index) => switchSlide(index)"
    

    to:

    @click="switchSlide(index)"