carousel

Carousel with thumbnail navigation

Carousel with thumbnail navigation

Загрузка превью…

src/carousel/c-carousel-9.vue

<script setup lang="ts">
import { ref, watch } from "vue"
import type { EmblaCarouselType } from "embla-carousel"
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel"
import { cn } from "@/lib/utils"

const PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
const ITEMS_COUNT = 10
const items = Array.from({ length: ITEMS_COUNT }, (_, index) => index)

const mainApi = ref<EmblaCarouselType>()
const thumbApi = ref<EmblaCarouselType>()
const selectedIndex = ref(1)
const bound = ref(false)

function onThumbClick(index: number) {
  if (!mainApi.value || !thumbApi.value) return
  mainApi.value.scrollTo(index)
}

watch(
  [mainApi, thumbApi],
  ([main, thumb]) => {
    if (!main || !thumb || bound.value) return
    bound.value = true
    const onSelect = () => {
      if (!mainApi.value || !thumbApi.value) return
      const index = mainApi.value.selectedScrollSnap()
      selectedIndex.value = index
      thumbApi.value.scrollTo(index)
    }
    onSelect()
    main.on("select", onSelect)
    main.on("reInit", onSelect)
  },
  { immediate: true }
)
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-3 p-4">
    <Carousel :set-api="(api) => (mainApi = api)" :opts="{ startIndex: 1, duration: 0 }" class="w-full">
      <CarouselContent>
        <CarouselItem v-for="index in items" :key="index">
          <div class="bg-muted relative aspect-video overflow-hidden rounded-xl">
            <img :src="PIXEL" :alt="`Slide ${index + 1}`" width="1000" height="800" class="h-full w-full object-cover" />
          </div>
        </CarouselItem>
      </CarouselContent>
    </Carousel>

    <Carousel :set-api="(api) => (thumbApi = api)" :opts="{ containScroll: 'keepSnaps', dragFree: true, duration: 0 }" class="w-full">
      <CarouselContent class="-ml-2 flex-row">
        <CarouselItem
          v-for="index in items"
          :key="index"
          class="basis-1/5 cursor-pointer pl-2 sm:basis-1/6"
          @click="onThumbClick(index)"
        >
          <div
            :class="
              cn(
                'relative aspect-square overflow-hidden rounded-lg border-2 transition-all',
                index === selectedIndex ? 'border-primary opacity-100' : 'border-transparent opacity-40 hover:opacity-70'
              )
            "
          >
            <img :src="PIXEL" :alt="`Thumb ${index + 1}`" width="400" height="400" class="h-full w-full object-cover" />
          </div>
        </CarouselItem>
      </CarouselContent>
    </Carousel>
  </div>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-carousel-9.json

Зависимости реестра

npm-зависимости

  • embla-carousel

Источник: порт из ReUI (Keenthemes, MIT)