Lzh on GitHub

Carousel

使用 Embla 构建的带运动和滑动手势的轮播组件。

用法

Items

使用 items prop 作为数组,并通过默认插槽渲染每个项目:

在桌面端,用鼠标拖动轮播图可水平滑动。
<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

你也可以传递一个包含以下属性的对象数组:

  • class?: any
  • ui?: { item?: ClassNameValue }

你可以通过在 item 上使用 basis / width 工具类来控制可见的项目数量:

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" :items="items" :ui="{ item: 'basis-1/3' }">
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>

方向 (Orientation)

使用 orientation prop 来改变进度条的方向。默认为 horizontal(水平)。

在桌面端,用鼠标拖动轮播图可垂直滑动。
<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    orientation="vertical"
    :items="items"
    class="w-full max-w-xs mx-auto"
    :ui="{ container: 'h-[336px]' }"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
在垂直方向上,你需要为容器指定一个 height

箭头 (Arrows)

使用 arrows prop 来显示上一个和下一个按钮。

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" arrows :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Prev / Next

使用 prevnext props 来自定义上一个和下一个按钮,可使用任何 Button 的 props。

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    arrows
    :prev="{ color: 'primary' }"
    :next="{ variant: 'solid' }"
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

Prev / Next Icons

使用 prev-iconnext-icon props 来自定义按钮的 Icon。默认为 i-lucide-arrow-left / i-lucide-arrow-right

<script setup lang="ts">
defineProps<{
  prevIcon?: string
  nextIcon?: string
}>()

const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    arrows
    :prev-icon="prevIcon"
    :next-icon="nextIcon"
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
你可以在 app.config.ts 中通过 ui.icons.arrowLeft / ui.icons.arrowRight 键全局自定义这些图标。
你可以在 vite.config.ts 中通过 ui.icons.arrowLeft / ui.icons.arrowRight 键全局自定义这些图标。

圆点 (Dots)

使用 dots prop 来显示一个圆点列表,用于滚动到特定幻灯片。

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" dots :items="items" class="w-full max-w-xs mx-auto">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

圆点的数量基于视图中显示的幻灯片数量:

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel v-slot="{ item }" dots :items="items" :ui="{ item: 'basis-1/3' }">
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

插件 (Plugins)

Carousel 组件实现了官方的 Embla Carousel plugins

自动播放 (Autoplay)

此插件用于扩展 Embla Carousel 的 自动播放 功能。

使用 autoplay prop 作为布尔值或对象来配置 Autoplay plugin

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    arrows
    dots
    :autoplay="{ delay: 2000 }"
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>
在此示例中,我们使用 loop prop 实现无限轮播。

自动滚动 (Auto Scroll)

此插件用于扩展 Embla Carousel 的 自动滚动 功能。

使用 auto-scroll prop 作为布尔值或对象来配置 Auto Scroll plugin

<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    dots
    arrows
    auto-scroll
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>
在此示例中,我们使用 loop prop 实现无限轮播。

自动高度 (Auto Height)

此插件用于扩展 Embla Carousel 的 自动高度 功能。它会改变轮播容器的高度以适应视图中最高幻灯片的高度。

使用 auto-height prop 作为布尔值或对象来配置 Auto Height plugin

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/320?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/320?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/320?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    auto-height
    arrows
    dots
    :items="items"
    :ui="{
      container: 'transition-[height]',
      controls: 'absolute -top-8 inset-x-12',
      dots: '-top-7',
      dot: 'w-6 h-1'
    }"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>
在此示例中,我们在容器上添加了 transition-[height] 类来动画化高度变化。

类名 (Class Names)

Class Names 是 Embla Carousel 的 类名切换 实用插件,它使你能够自动化切换轮播图上的类名。

使用 class-names prop 作为布尔值或对象来配置 Class Names plugin

<script setup lang="ts">
const items = [
  'https://picsum.photos/528/528?random=1',
  'https://picsum.photos/528/528?random=2',
  'https://picsum.photos/528/528?random=3',
  'https://picsum.photos/528/528?random=4',
  'https://picsum.photos/528/528?random=5',
  'https://picsum.photos/528/528?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    class-names
    arrows
    :items="items"
    :ui="{
      item: 'basis-[70%] transition-opacity [&:not(.is-snapped)]:opacity-10'
    }"
    class="mx-auto max-w-sm"
  >
    <img :src="item" width="264" height="264" class="rounded-lg">
  </UCarousel>
</template>
在此示例中,我们在 item 上添加了 transition-opacity [&:not(.is-snapped)]:opacity-10 类来动画化不透明度变化。

淡入淡出 (Fade)

此插件用于将 Embla Carousel 的滚动功能替换为 淡入淡出过渡

使用 fade prop 作为布尔值或对象来配置 Fade plugin

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    fade
    arrows
    dots
    :items="items"
    class="w-full max-w-xs mx-auto"
  >
    <img :src="item" width="320" height="320" class="rounded-lg">
  </UCarousel>
</template>

滚轮手势 (Wheel Gestures)

此插件用于扩展 Embla Carousel,使其能够 使用鼠标/触控板滚轮 导航轮播图。

使用 wheel-gestures prop 作为布尔值或对象来配置 Wheel Gestures plugin

使用鼠标滚轮滚动轮播图。
<script setup lang="ts">
const items = [
  'https://picsum.photos/468/468?random=1',
  'https://picsum.photos/468/468?random=2',
  'https://picsum.photos/468/468?random=3',
  'https://picsum.photos/468/468?random=4',
  'https://picsum.photos/468/468?random=5',
  'https://picsum.photos/468/468?random=6'
]
</script>

<template>
  <UCarousel
    v-slot="{ item }"
    loop
    wheel-gestures
    :items="items"
    :ui="{ item: 'basis-1/3' }"
  >
    <img :src="item" width="234" height="234" class="rounded-lg">
  </UCarousel>
</template>

示例

带缩略图

你可以使用 emblaApi 函数 scrollTo 在轮播图下方显示缩略图,允许你导航到特定幻灯片。

<script setup lang="ts">
const items = [
  'https://picsum.photos/640/640?random=1',
  'https://picsum.photos/640/640?random=2',
  'https://picsum.photos/640/640?random=3',
  'https://picsum.photos/640/640?random=4',
  'https://picsum.photos/640/640?random=5',
  'https://picsum.photos/640/640?random=6'
]

const carousel = useTemplateRef('carousel')
const activeIndex = ref(0)

function onClickPrev() {
  activeIndex.value--
}
function onClickNext() {
  activeIndex.value++
}
function onSelect(index: number) {
  activeIndex.value = index
}

function select(index: number) {
  activeIndex.value = index

  carousel.value?.emblaApi?.scrollTo(index)
}
</script>

<template>
  <div class="flex-1 w-full">
    <UCarousel
      ref="carousel"
      v-slot="{ item }"
      arrows
      :items="items"
      :prev="{ onClick: onClickPrev }"
      :next="{ onClick: onClickNext }"
      class="w-full max-w-xs mx-auto"
      @select="onSelect"
    >
      <img :src="item" width="320" height="320" class="rounded-lg">
    </UCarousel>

    <div class="flex gap-1 justify-between pt-4 max-w-xs mx-auto">
      <div
        v-for="(item, index) in items"
        :key="index"
        class="size-11 opacity-25 hover:opacity-100 transition-opacity"
        :class="{ 'opacity-100': activeIndex === index }"
        @click="select(index)"
      >
        <img :src="item" width="44" height="44" class="rounded-lg">
      </div>
    </div>
  </div>
</template>

API

Props

Prop Default Type
as

'div'

any

The element or component this component should render as.

prev

{ size: 'md', color: 'neutral', variant: 'link' }

ButtonProps

Configure the prev button when arrows are enabled.

prevIcon

appConfig.ui.icons.arrowLeft

string

The icon displayed in the prev button.

next

{ size: 'md', color: 'neutral', variant: 'link' }

ButtonProps

Configure the next button when arrows are enabled.

nextIcon

appConfig.ui.icons.arrowRight

string

The icon displayed in the next button.

arrows

false

boolean

Display prev and next buttons to scroll the carousel.

dots

false

boolean

Display dots to scroll to a specific slide.

orientation

'horizontal'

"horizontal" | "vertical"

The orientation of the carousel.

items

CarouselItem[]

autoplay

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Autoplay plugin

autoScroll

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Auto Scroll plugin

autoHeight

false

boolean | Partial<CreateOptionsType<{ active: boolean; breakpoints: { [key: string]: Omit<Partial<...>, "breakpoints">; }; }>>

Enable Auto Height plugin

classNames

false

boolean | Partial<CreateOptionsType<OptionsType>>

Enable Class Names plugin

fade

false

boolean | Partial<CreateOptionsType<{ active: boolean; breakpoints: { [key: string]: Omit<Partial<...>, "breakpoints">; }; }>>

Enable Fade plugin

wheelGestures

false

any

Enable Wheel Gestures plugin

active

true

boolean

duration

25

number

align

'center'

"center" | "start" | "end" | (viewSize: number, snapSize: number, index: number): number

containScroll

'trimSnaps'

false | "trimSnaps" | "keepSnaps"

slidesToScroll

1

number | "auto"

dragFree

false

boolean

dragThreshold

10

number

inViewThreshold

0

number | number[]

loop

false

boolean

skipSnaps

false

boolean

startIndex

0

number

watchDrag

true

false | true | (emblaApi: EmblaCarouselType, evt: PointerEventType): boolean | void

watchResize

true

false | true | (emblaApi: EmblaCarouselType, entries: ResizeObserverEntry[]): boolean | void

watchSlides

true

false | true | (emblaApi: EmblaCarouselType, mutations: MutationRecord[]): boolean | void

watchFocus

true

false | true | (emblaApi: EmblaCarouselType, evt: FocusEvent): boolean | void

breakpoints

{}

{ [key: string]: Omit<Partial<CreateOptionsType<{ align: AlignmentOptionType; axis: AxisOptionType; container: string | HTMLElement | null; slides: string | HTMLElement[] | NodeListOf<...> | null; ... 13 more ...; watchFocus: FocusHandlerOptionType; }>>, "breakpoints">; }

ui

{ root?: ClassNameValue; viewport?: ClassNameValue; container?: ClassNameValue; item?: ClassNameValue; controls?: ClassNameValue; ... 4 more ...; dot?: ClassNameValue; }

Slots

Slot Type
default

{ item: CarouselItem; index: number; }

Emits

Event Type
select

number

Expose

你可以使用 useTemplateRef 访问类型化的组件实例。

<script setup lang="ts">
const carousel = useTemplateRef('carousel')
</script>

<template>
  <UCarousel ref="carousel" />
</template>

这将使你能够访问以下内容:

NameType
emblaRefRef<HTMLElement | null>
emblaApiRef<EmblaCarouselType | null>

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    carousel: {
      slots: {
        root: 'relative focus:outline-none',
        viewport: 'overflow-hidden',
        container: 'flex items-start',
        item: 'min-w-0 shrink-0 basis-full',
        controls: '',
        arrows: '',
        prev: 'absolute rounded-full',
        next: 'absolute rounded-full',
        dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3',
        dot: [
          'cursor-pointer size-3 bg-accented rounded-full',
          'transition'
        ]
      },
      variants: {
        orientation: {
          vertical: {
            container: 'flex-col -mt-4',
            item: 'pt-4',
            prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
            next: 'bottom-4 sm:-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90'
          },
          horizontal: {
            container: 'flex-row -ms-4',
            item: 'ps-4',
            prev: 'start-4 sm:-start-12 top-1/2 -translate-y-1/2',
            next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2'
          }
        },
        active: {
          true: {
            dot: 'data-[state=active]:bg-inverted'
          }
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        carousel: {
          slots: {
            root: 'relative focus:outline-none',
            viewport: 'overflow-hidden',
            container: 'flex items-start',
            item: 'min-w-0 shrink-0 basis-full',
            controls: '',
            arrows: '',
            prev: 'absolute rounded-full',
            next: 'absolute rounded-full',
            dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3',
            dot: [
              'cursor-pointer size-3 bg-accented rounded-full',
              'transition'
            ]
          },
          variants: {
            orientation: {
              vertical: {
                container: 'flex-col -mt-4',
                item: 'pt-4',
                prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
                next: 'bottom-4 sm:-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90'
              },
              horizontal: {
                container: 'flex-row -ms-4',
                item: 'ps-4',
                prev: 'start-4 sm:-start-12 top-1/2 -translate-y-1/2',
                next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2'
              }
            },
            active: {
              true: {
                dot: 'data-[state=active]:bg-inverted'
              }
            }
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        carousel: {
          slots: {
            root: 'relative focus:outline-none',
            viewport: 'overflow-hidden',
            container: 'flex items-start',
            item: 'min-w-0 shrink-0 basis-full',
            controls: '',
            arrows: '',
            prev: 'absolute rounded-full',
            next: 'absolute rounded-full',
            dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3',
            dot: [
              'cursor-pointer size-3 bg-accented rounded-full',
              'transition'
            ]
          },
          variants: {
            orientation: {
              vertical: {
                container: 'flex-col -mt-4',
                item: 'pt-4',
                prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90',
                next: 'bottom-4 sm:-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90'
              },
              horizontal: {
                container: 'flex-row -ms-4',
                item: 'ps-4',
                prev: 'start-4 sm:-start-12 top-1/2 -translate-y-1/2',
                next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2'
              }
            },
            active: {
              true: {
                dot: 'data-[state=active]:bg-inverted'
              }
            }
          }
        }
      }
    })
  ]
})