Vue3(CompositionAPI)で複数スライダーを動的に作成する方法
Vue3(CompositionAPI)で複数スライダーを動的に作成する方法を紹介します。
Vue3(CompositionAPI)で複数スライダーを動的に作成する方法
- Home.vue
- Carousel.vue
- CarouselSlide.vue
CarouselとCarouselSlideについては、Vue 3(composition API)でカルーセルスライダーを作る方法とほぼ同じなので、参考にしてみてください。
Home.vue
template
  <section class="home section bd-container">
    <span class="section-subtitle">sub title</span>
    <h2 class="section-title">home</h2>
    <!-- ナブバー -->
    <div class="home__nav" ref="home">
      <span class="home__item" @click="showAll">All</span>
      <span class="home__item" @click="FilterCatOne" >カテゴリー1</span>
      <span class="home__item" @click="FilterCatTwo">カテゴリー2</span>
    </div>
    <!-- ナブアイテム -->
    <transition-group
      class="home__container bd-grid"
      tag="div"
      appear
      name="home__list"
      @before-enter="beforeEnter"
      @enter="enter"
    >
      <div
        class="home__content"
        v-for="(item, filteredItemIndex) in filteredItems"
        :key="item"
        :class="{'is-hidden' : item.isHidden}">
        
        <Carousel
            @next="next"
            @prev="prev"
            :clickedFilteredItemIndex="filteredItemIndex"
          >
            <CarouselSlide
              v-for="(slide, index) in item.img"
              :key="slide"
              :index="index"
              :visibleSlide="visibleSlide"
              :direction="direction"
              >
              <img :src="require(`@/assets/img/${slide}.jpg`)" class="home__img" :alt="slide">
            </CarouselSlide>
          </Carousel>
        <div class="home__data">
          <span class="home__subtitle">{{ item.label }}</span>
          <a href="#"><h2 class="home__title">{{ item.title }}</h2></a>
          <p class="home__skills">{{ item.notes }}</p>
          <a href="#" class="button button-link">詳しく見てみる</a>
        </div>
      </div>
    </transition-group>
  </section>script
import Carousel from '@/components/home/Carousel.vue'
import CarouselSlide from '@/components/home/CarouselSlide.vue'
import { ref } from '@vue/reactivity'
import { onMounted } from 'vue'
import gsap from 'gsap'
export default {
  components: {
    Carousel,
    CarouselSlide,
  },
  setup() {
    // データ
    const homeItems = ref([
      {
       id: 1,
       label: 'カテゴリー1',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-01',
         '0-02',
         '0-03',
         '0-04',
       ],
       isHidden: false},
      {
       id: 2,
       label: 'カテゴリー2',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-01',
         '0-02',
         '0-03',
         '0-04',
       ],
       isHidden: false},
      {
       id: 3,
       label: 'カテゴリー1',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-01',
         '0-02',
         '0-03',
         '0-04',
       ],
       isHidden: false},
      {
       id: 4,
       label: 'カテゴリー2',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-05',
         '0-06',
         '0-07',
         '0-08',
       ],
       isHidden: false},
      {
       id: 5,
       label: 'カテゴリー1',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-01',
         '0-02',
         '0-03',
         '0-04',
       ],
       isHidden: false},
      {
       id: 6,
       label: 'カテゴリー2',
       title: 'タイトル',
       notes: 'ノート',
       img: [
         '0-05',
         '0-06',
         '0-07',
         '0-08',
       ],
       isHidden: false},
    ])
    // データの表示用
    const filteredItems = ref(null)
    // ナブアイテム
    const home = ref(null)
    // クリックされたナブアイテムをアクティブにする
    const activeNav = (el) => {
      home.value.childNodes.forEach(e => {
        e.className = 'home__item'
      })
      el.currentTarget.className = 'home__item home__nav-active'
    }
  // カテゴリー1を優先表示
  const FilterCatOne = (el) => {
    homeItems.value.filter(item => {
      if(item.label !== 'カテゴリー1') {
        item.isHidden = true
      } else {
        item.isHidden = false
      }
    })
    // 並び替えtrue,falseの順
    filteredItems.value = [...homeItems.value].sort((a,b) => a.isHidden - b.isHidden)
    activeNav(el)
  }
  // カテゴリー2を優先表示
  const FilterCatTwo = (el) => {
    homeItems.value.filter(item => {
      if(item.label !== 'カテゴリー2') {
        item.isHidden = true
      } else {
        item.isHidden = false
      }
    })
    // 並び替えtrue,falseの順
    filteredItems.value = [...homeItems.value].sort((a,b) => a.isHidden - b.isHidden)
    
    activeNav(el)
  }
    // すべてを表示(id順)
    const showAll = (el) => {
      homeItems.value.filter(item => {
        item.isHidden = false
      })
      // id順に並び替え
      filteredItems.value = [...homeItems.value].sort((a,b) => a.id - b.id)
      activeNav(el)
    }
    // 交差後のすぐに発火するアニメーション
    const beforeEnter = (el) => {
      el.style.opacity = 0
    }
    // 交差後のすぐに発火するアニメーション
    const enter = (el,done) => {
      gsap.to(el, {
        opacity: 1,
        duration:0.5,
        ease: 'circ.out',
        onComplete: done,
      })
    }
    // indexとイコールの変数を作成する
    const visibleSlide = ref(0)
    // indexには、Carousel.vueでクリックされたボタンのindexが渡る
    const slidesLength = (clickedFilteredItemIndex) => {
     return filteredItems.value[clickedFilteredItemIndex].img.length
    }
    // ボタン、left,rightを入れ替えるための変数
    const direction = ref('left')
    // Carouselから受け取ったnext
    const next = (clickedFilteredItemIndex) => {
      if(visibleSlide.value >= slidesLength(clickedFilteredItemIndex) - 1) {
        visibleSlide.value = 0
      } else {
        visibleSlide.value++
      }
      
      direction.value = 'carousel__left'
    }
    // Carouselから受け取ったprev
    const prev = (clickedFilteredItemIndex) => {
      if(visibleSlide.value <= 0) { visibleSlide.value = slidesLength(clickedFilteredItemIndex) - 1 } else { visibleSlide.value-- } direction.value = 'carousel__right' } onMounted(() => {
      
      let options = {
        rootMargin: '0px',
        threshold: 1.0
      }
      const observer = new IntersectionObserver(function(entries) {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
           filteredItems.value = homeItems.value
           observer.disconnect()
          }
        })
      }, options)
      observer.observe(home.value)
    })
    return {
      homeItems,
      home,
      FilterCatOne,
      FilterCatTwo,
      filteredItems,
      showAll,
      beforeEnter,
      enter,
      activeNav,
      visibleSlide,
      direction,
      next,
      prev,
    }
  }
}ナブバーのアイテムをクリックされた要素のスタイルを変化させる
    <!-- ナブバー -->
    <div class="home__nav" ref="home">
      <span class="home__item" @click="showAll">All</span>
      <span class="home__item" @click="FilterCatOne" >カテゴリー1</span>
      <span class="home__item" @click="FilterCatTwo">カテゴリー2</span>
    </div>ナビゲーションのバーとアイテムを作成しています。
クリックされたらそれぞれのメソッドを発動させます。
    // クリックされたナブアイテムをアクティブにする
    const activeNav = (el) => {
      home.value.childNodes.forEach(e => {
        e.className = 'home__item'
      })
      el.currentTarget.className = 'home__item home__nav-active'
    }クリックされたアイテムをアクティブにするため、
classNameかclassList.addを使って、クリックされたアイテムのクラスを変更します。
indexには、Carousel.vueでクリックされたボタンのindexが渡ります。
// indexには、Carousel.vueでクリックされたボタンのindexが渡る
const slidesLength = (clickedFilteredItemIndex) => {
return filteredItems.value[clickedFilteredItemIndex].img.length
}子コンポーネントCarousel.vueからクリックされたもののインデックス番号を取得する
まず、子コンポーネントCarouselにindex番号を渡します。
そして、子コンポーネントでボタンを作成しているので、
それをクリックしたら、そのクリックされたもののインデックスが取得できるように、
nextとprevメソッドで渡します。
propsを登録し、setup内で渡します。
props.のかたちでアクセスできます。
親ではemitされたメソッドで、同時に渡される引数を確認する
    // indexには、Carousel.vueでクリックされたボタンのindexが渡る
    const slidesLength = (clickedFilteredItemIndex) => {
     return filteredItems.value[clickedFilteredItemIndex].img.length
    }そして、これを条件に使って、画像の長さを取得します。
    // Carouselから受け取ったnext
    const next = (clickedFilteredItemIndex) => {
      if(visibleSlide.value >= slidesLength(clickedFilteredItemIndex) - 1) {
        visibleSlide.value = 0
      } else {
        visibleSlide.value++
      }
      
      direction.value = 'carousel__left'
    }
    // Carouselから受け取ったprev
    const prev = (clickedFilteredItemIndex) => {
      if(visibleSlide.value <= 0) {
        visibleSlide.value = slidesLength(clickedFilteredItemIndex) - 1
      } else {
        visibleSlide.value--
      }
      direction.value = 'carousel__right'
    }画像の長さから-1した数値が最大になるので、その場合とそれ以外で条件分岐します。