Search code examples
javascriptarraysvue.jsvue-componentvuejs3

How to make this carousel unreliable on id assigned in props?


I've recently build a carousel with slots. But it has one issue. This component is based on the fact that id for each slide is defined as a prop, so code looks more complicated and I'm sure that there's simpler way to do it.

<template>
    <div class="carousel">
        
        <slot class="image"></slot>
        <div class="arrow left" @click="prev"
        :class="{ 'invisible': !hasPrev() }">
        <svg width="80px" height="80px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M13.9783 5.31877L10.7683 8.52877L8.79828 10.4888C7.96828 11.3188 7.96828 12.6688 8.79828 13.4988L13.9783 18.6788C14.6583 19.3588 15.8183 18.8688 15.8183 17.9188V12.3088V6.07877C15.8183 5.11877 14.6583 4.63877 13.9783 5.31877Z" fill="#292D32"></path> </g></svg></div>
         <div class="arrow right" @click="next"
         :class="{ 'invisible': !hasNext() }"><svg width="80px" height="80px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" transform="rotate(180)"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M13.9783 5.31877L10.7683 8.52877L8.79828 10.4888C7.96828 11.3188 7.96828 12.6688 8.79828 13.4988L13.9783 18.6788C14.6583 19.3588 15.8183 18.8688 15.8183 17.9188V12.3088V6.07877C15.8183 5.11877 14.6583 4.63877 13.9783 5.31877Z" fill="#292D32"></path> </g></svg></div>
    </div>

</template>
<script>
import { ref } from "vue";
import { provide } from "vue";
export default {
  setup(props, { slots }) {
    const carouselIds = ref(slots.default().map((slide) => slide.props.id));
    const selectedId = ref(carouselIds.value[0]);
    provide("selectedId", selectedId);

    return {
        carouselIds,
        selectedId,
    };
  },
  data(){
    return{
        id:1
    }
  },
  methods:{
    hasNext() {
            return this.id  < this.carouselIds.length;
        },
        hasPrev() {
            return this.id - 1 >=  1
        },
        prev() {
            if (this.hasPrev()) {
                this.selectedId = this.id-=1
            }
        },
        next() {
            if (this.hasNext()) {
                this.selectedId = this.id+=1;
            }
        }
  },

};

</script>
<style scoped>
.invisible {
    visibility: hidden;
}
.carousel{
    position: relative;

}
.arrow{
    cursor: pointer;
    display: block;
    position: absolute;
    top: 50%;
  transform: translateY(-50%);
}


.arrow.right{
    right: 0;
}
.arrow.left{
    left: 0;
}
</style>

This is my main Carousel component where each slide is slot inside this Carousel. As you see, by extracting id defined as a prop in each slide component, it creates a new array with each id assigned to each slide in array. By clicking on buttons "next", "prev", it changes id, so it assigns the next slide that has the same id as id that we've incremented.

<template>
    <div class="slide" v-show="id == selectedId">

        <slot/>
    </div>
    
</template>
<script>
import { inject } from 'vue';
export default{
    props:['id'],
    setup(){
        const selectedId=inject('selectedId')

        return{
            selectedId,
        }
    },
    
  <Carousel>
            <Slide id="1">
<img src="https://www.freecodecamp.org/news/content/images/size/w2000/2022/08/Vue-Blog-Cover-2.png">
            </Slide>
            <Slide id="2">
<img src="https://community-cdn-digitalocean-com.global.ssl.fastly.net/snN3rbgKF7McfuiQAKcoLWMn'">
            </Slide >
        </Carousel>

I want to somehow extract indices of all elements in my array and then create a new array with these indices, so I can implement the same method without assigning id for each slide.

My code in SFC playground


Solution

  • If you want the Carousel to be responsible to show / hide sliders depending on the selectedId, you could use a render function to dynamically add a css class to display each slider :

    import { h } from 'vue'
    
    export default {
      props: {
        selectedId: {
          type: Number,
          required: true
        }
      },
      render() {
        const slides = this.$slots.default()[0].children
        const render = []
        
        slides.forEach((slide, index) => {
          render.push(h('div', { class: this.selectedId === index ? '' : 'hidden'}, slide))
        })
        
        return render
      }
    }
    

    I have adapted your SFC playground accordingly.

    You can also insert slides in the dom only when user is navigating, which would result of a kind of "lazy loading" :

      render() {
        const slide = this.$slots.default()[0].children[this.selectedId]
        
        return h('div', slide)
      }