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