carousel

Carousel with overlay thumbnail navigation

Carousel with overlay thumbnail navigation

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

src/carousel/c-carousel-10.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-2xl items-center justify-center p-4">
    <div class="group relative w-full overflow-hidden rounded-xl">
      <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 w-full overflow-hidden">
              <img :src="PIXEL" :alt="`Slide ${index + 1}`" width="1200" height="675" class="h-full w-full object-cover" />
            </div>
          </CarouselItem>
        </CarouselContent>
      </Carousel>

      <div class="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/60 to-transparent p-4 transition-opacity duration-300">
        <div class="relative mx-auto w-full max-w-md">
          <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/4 cursor-pointer pl-2 sm:basis-1/8"
                @click="onThumbClick(index)"
              >
                <div
                  :class="
                    cn(
                      'relative aspect-square overflow-hidden rounded-md border-2 transition-all duration-300',
                      index === selectedIndex ? 'border-white opacity-100 ring-2 ring-black/20' : 'border-white/40 opacity-50 hover:opacity-80'
                    )
                  "
                >
                  <img :src="PIXEL" :alt="`Thumb ${index + 1}`" width="200" height="200" class="h-full w-full object-cover" />
                </div>
              </CarouselItem>
            </CarouselContent>
          </Carousel>
        </div>
      </div>
    </div>
  </div>
</template>

Установка

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

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

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

  • embla-carousel

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