🥷

vue3-carouselでサクっとカルーセルを作る

2023/08/29に公開

最近ニンジャなら無料のゲームにハマっているのでアイコンはニンジャです。

vue3-carouselというgithubで500~⭐を獲得している優秀なプラグインがあったので,実際の使い勝手を書き留めておくことにしました。
Githubrepoはこちら

出来ること

Vue3で簡単にカルーセルを作れる

実行環境

nuxt3 Tailwindcssを使っています

使用例

公式の例はv-for="slide in 10"と,単に1から10までの数字データを表示させているだけなので,著作権的に物議を醸しそうな創作音楽ゲームのジャケットをx-アプリっぽく表示して,クリックするとリンク先に飛ぶようなものを作ってみました。

公式のExampleが豊富で,これをコピぺ改変すれば簡単にアレンジが効くのが良いと思います。

出来たやつ(自分のgithub pages)

全コード(長いので折り畳み)
RhythmGameSlide.vue
<script setup>
import { Carousel,Navigation, Pagination, Slide } from 'vue3-carousel'

import 'vue3-carousel/dist/carousel.css'

const slides = [
  {image:'画像のパス',song:'曲名',url:'リンク先'},
  //以下好きなだけ連想配列を同様に書く(APIがないため手動)
]

const songName = ref('')

function changeSongName(song){
  songName.value = song
}

</script>

<template>
  <Carousel :autoplay="2500" :pauseAutoplayOnHover="true" :itemsToShow="3.95" :wrapAround="true" :transition="500">
    <Slide v-for="slide in slides" :key="slide">
      <div class="carousel__item relative">
        <SlideContent :slide="slide" @hovered="changeSongName" @unhovered="changeSongName('')" />
      </div>
    </Slide>
    <template #addons>
      <Pagination />
      <Navigation />
    </template>
  </Carousel>
  <p class="text text-center">{{ songName }}</p>
</template>


<style scoped>
.carousel__slide {
  padding: 5px;
}

.carousel__viewport {
  perspective: 2000px;
}

.carousel__track {
  transform-style: preserve-3d;
}

.carousel__slide--sliding {
  transition: 0.5s;
}

.carousel__slide {
  opacity: 0.9;
  transform: rotateY(-20deg) scale(0.9);
}

.carousel__slide--active ~ .carousel__slide {
  transform: rotateY(20deg) scale(0.9);
}

.carousel__slide--prev {
  opacity: 1;
  transform: rotateY(-10deg) scale(0.95);
}

.carousel__slide--next {
  opacity: 1;
  transform: rotateY(10deg) scale(0.95);
}

.carousel__slide--active {
  opacity: 1;
  transform: rotateY(0) scale(1.1);
}
</style>
SlideContent.vue
<script setup>
const props = defineProps({
  slide: Object,
})

</script>

<template>
  <NuxtLink :to="slide.url">
    <img @mouseover="$emit('hovered',slide.song)" @mouseleave="$emit('unhovered')" :src="slide.image"
      class="rounded-lg object-fill h-40 w-40" />
  </NuxtLink>
</template>

まずvue3-carouselのコンポーネント群とcssをimportします。

import { Carousel,Navigation, Pagination, Slide } from 'vue3-carousel'

import 'vue3-carousel/dist/carousel.css'

カルーセル部分はこうなっています。

  <Carousel :autoplay="2500" :pauseAutoplayOnHover="true" :itemsToShow="3.95" :wrapAround="true" :transition="500">
    <Slide v-for="slide in slides" :key="slide">
      <div class="carousel__item relative">
        <SlideContent :slide="slide" @hovered="changeSongName" @unhovered="changeSongName('')" />
      </div>
    </Slide>
    <template #addons>
      <Pagination />
      <Navigation />
    </template>
  </Carousel>

Carouselコンポーネントに渡しているpropsの説明

  • autoplay:指定した数字msおきに自動でスライドさせます。0にするとautoplayしません。
  • pauseAutoplayOnHover:カーソルを乗せるとautoplayが止まります。
  • itemsToShow:画面上にどれくらい要素を表示するか決めます。今回は3.95なので,ほぼ4つのジャケットが表示(一つは若干欠けている)されています。
  • wrapAround:要素がループするようになります。
  • transition:要素がスライドインしてくる時間です(ms)

オンラインゲームの公式HPのお知らせっぽいカルーセルにしたいなら:itemsToShow="1.00" wrapAround="true"とすると出来ます。

Slideコンポーネントのcssの仕組み

Slideのコンポーネントは次のようになっています。

Slide.ts
import { defineComponent, inject, ref, h, reactive, SetupContext, computed, ComputedRef } from 'vue'

import { defaultConfigs } from '@/partials/defaults'
import { CarouselConfig } from '@/types'

export default defineComponent({
  name: 'CarouselSlide',
  props: {
    index: {
      type: Number,
      default: 1,
    },
    isClone: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, { slots }: SetupContext) {
    const config: CarouselConfig = inject('config', reactive({ ...defaultConfigs }))
    const currentSlide = inject('currentSlide', ref(0))
    const slidesToScroll = inject('slidesToScroll', ref(0))
    const isSliding = inject('isSliding', ref(false))

//ここら辺の値が何をしているかは察しがつくはず
    const isActive: ComputedRef<boolean> = computed(
      () => props.index === currentSlide.value
    )
    const isPrev: ComputedRef<boolean> = computed(
      () => props.index === currentSlide.value - 1
    )
    const isNext: ComputedRef<boolean> = computed(
      () => props.index === currentSlide.value + 1
    )
    const isVisible: ComputedRef<boolean> = computed(() => {
      const min = Math.floor(slidesToScroll.value)
      const max = Math.ceil(slidesToScroll.value + config.itemsToShow - 1)

      return props.index >= min && props.index <= max
    })

    return () =>
      h(
        'li',
        {
          style: {width: `${100 / config.itemsToShow}%`},
          class: {
            carousel__slide: true,
            'carousel__slide--clone': props.isClone,
            'carousel__slide--visible': isVisible.value,
            'carousel__slide--active': isActive.value,
            'carousel__slide--prev': isPrev.value,
            'carousel__slide--next': isNext.value,
            'carousel__slide--sliding': isSliding.value,
          },
          'aria-hidden': !isVisible.value,
        },
        slots.default?.({
          isActive: isActive.value,
          isClone: props.isClone,
          isPrev: isPrev.value,
          isNext: isNext.value,
          isSliding: isSliding.value,
          isVisible: isVisible.value
        })
      )
  },
})

重要となってくるのがレンダー関数で<li>要素として出力されている以下の部分で,それぞれ値が配列内のキーに該当する要素に指定のclass名を割り当てる構造になっています。

          class: {
            carousel__slide: true,
            'carousel__slide--clone': props.isClone,
            'carousel__slide--visible': isVisible.value,
            'carousel__slide--active': isActive.value,
            'carousel__slide--prev': isPrev.value,
            'carousel__slide--next': isNext.value,
            'carousel__slide--sliding': isSliding.value,
          },

公式Docsにはこの詳細は書いていなかったですが,カルーセル内の特定の要素にcssやtailwindcssを@applyしたい場合はこれらの値を使う必要があります。
今回の例の場合(公式Exampleのコピペ)

<style scoped> //他にもカルーセルを使っている場合にcssが競合しないようにするscoped
.carousel__slide {
  padding: 5px;
}

.carousel__viewport {
  perspective: 2000px;
}

.carousel__track {
  transform-style: preserve-3d;
}

.carousel__slide--sliding {
  transition: 0.5s;
}

.carousel__slide {
  opacity: 0.9; //pagenationのボタンの裏に隠れるような見た目になる
  transform: rotateY(-20deg) scale(0.9); 
//rotateYを付けることで,左右にスライドするというより緩やかな楕円を描くような見た目になる
}

.carousel__slide--active ~ .carousel__slide {
  transform: rotateY(20deg) scale(0.9);
}

.carousel__slide--prev {
  opacity: 1;
  transform: rotateY(-10deg) scale(0.95);
}

.carousel__slide--next {
  opacity: 1;
  transform: rotateY(10deg) scale(0.95);
}

.carousel__slide--active {
  opacity: 1;
  transform: rotateY(0) scale(1.1); //scaleの値を適宜調整する
}
</style>

pagenationとnavigationについて

以下をCarouselコンポーネント内にネストすると,左右にスライドさせるボタン及び中央下部に任意のスライドにジャンプできる&現在のスライド番号の色が濃くなっているアレを表示してくれます。

    <template #addons>
      <Pagination />
      <Navigation />
    </template>

カルーセルの挙動について

特にDocsには書いてなかったのですが,カルーセルにはwidth:100%/w-fullが付いていて,スライド内の要素はitemsToShowで指定した値が要素の大きさ(画像サイズなど)を参照し等間隔となるように表示されます。

works.vue
      <div class="w-1/2 mt-4"> //ここの値を変えてみる
        <RhythmGameSlide />// カルーセルのコンポーネント
      </div>
widthを色々変えた時のスクショ

適当(w-1/2)

間隔が広すぎ(w-full)

狭すぎて画像が縦長になる(w-1/4)

適切な幅を設定した<div>要素にカルーセルをネストしてあげる必要があります。

終わり

学習時間2時間くらいで,簡単なvue.jsの知識があればすぐにイカしたカルーセルを置くことが出来る非常に良いプラグインだと思います。カルーセルを置きたいとvue3/nuxt3環境で思ったらまずこれで見た目等満足できないかを検討したほうが良いと思います。

Discussion