Vue3(CompositionAPI)で複数スライダーを動的に作成する方法

Vue.js

Vue3(CompositionAPI)で複数スライダーを動的に作成する方法を紹介します。
Githubにソースコードを上げています。
https://github.com/devsakaso/vue3-multi-sliders

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した数値が最大になるので、その場合とそれ以外で条件分岐します。

Vue.js

Posted by devsakaso