Element Plus Carousel 轮播组件完全指南:从基础用法到源码级原理
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
轮播图(Carousel)是在有限空间内循环展示一系列图片或文本的经典 UI 组件。本文将基于 Element Plus 官方文档 carousel.md 的核心脉络,结合仓库中packages/components/carousel的真实源码实现,系统讲解el-carousel与el-carousel-item的基础用法、全部配置项、事件、插槽与实例方法,并深入剖析其自动播放、无限循环、卡片模式、运动模糊等特性的底层实现原理。读完本文,你将能够熟练使用该组件完成从简单 Banner 到复杂卡片轮播的各类实战场景,并理解其内部工作机制。
一、组件概述与基本用法
ElCarousel是一个复合组件,由外层容器el-carousel和若干个轮播项el-carousel-item组成。官方文档这样定义它的用途:在一个受限的空间内循环展示一系列图片或文本。
最基本的用法是将两者组合使用,每个el-carousel-item中的内容完全自定义,只需把任意内容放进标签内部即可。默认情况下,当鼠标悬停在指示器(indicator,即底部的小圆点)上时轮播会自动切换;将trigger设置为click后,只有点击指示器才会切换。
<template> <el-carousel> <el-carousel-item v-for="item in 4" :key="item"> <h3 class="small justify-center" text="2xl">{{ item }}</h3> </el-carousel-item> </el-carousel> </template> <style scoped> .el-carousel__item h3 { color: #475669; opacity: 0.75; line-height: 300px; margin: 0; text-align: center; } .el-carousel__item:nth-child(2n) { background-color: #99a9bf; } .el-carousel__item:nth-child(2n + 1) { background-color: #d3dce6; } </style>完整示例可参考 docs/examples/carousel/basic.vue。
组件注册说明
在 Element Plus 中,el-carousel和el-carousel-item均通过组件库统一注册。查看 组件目录 可知,该目录向外导出ElCarousel与ElCarouselItem两个组件。如果你使用按需引入(unplugin-vue-components 或手动import),请确保两个组件都被引入;如果采用全量引入(import ElementPlus from 'element-plus'),则无需额外操作。
从源码结构看,整个轮播模块由以下文件组成,本文后续会逐一引用:
- carousel.vue:容器组件模板与逻辑
- carousel-item.vue:轮播项组件模板
- carousel.ts:Props 与事件类型定义
- use-carousel.ts:容器核心逻辑(组合式函数)
- use-carousel-item.ts:轮播项核心逻辑
- constants.ts:父子组件通信的
provide/inject上下文类型 - instance.ts:实例类型声明
- carousel.test.tsx:组件测试
二、Carousel Attributes 全参数详解
官方文档给出了完整的参数表,这些参数在 carousel.ts 中都有对应的类型定义与默认值,两者完全对应:
| 参数名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
height | 轮播的高度 | string | '' |
initial-index | 初始激活的轮播项索引(从 0 开始) | number | 0 |
trigger | 指示器的触发方式 | 'hover' \| 'click' | hover |
autoplay | 是否自动循环播放 | boolean | true |
interval | 自动播放间隔(毫秒) | number | 3000 |
indicator-position | 指示器的位置 | '' \| 'none' \| 'outside' | '' |
arrow | 箭头显示的时机 | 'always' \| 'hover' \| 'never' | hover |
type | 轮播类型 | '' \| 'card' | '' |
card-scale(^2.7.8) | 卡片模式下,非激活副卡片的缩放比例 | number | 0.83 |
loop | 是否循环展示 | boolean | true |
direction | 展示方向 | 'horizontal' \| 'vertical' | horizontal |
pause-on-hover | 鼠标悬停时是否暂停自动播放 | boolean | true |
motion-blur(^2.6.0) | 是否为轮播注入动态模糊效果 | boolean | false |
下面逐一展开讲解各参数的用法与底层实现。
2.1 height:固定高度与自适应高度
height决定轮播容器的高度,默认空字符串。当设置为auto时,容器高度会根据当前激活轮播项的实际高度自动调整,这是「Auto height」特性的核心。在 use-carousel.ts 中,容器样式通过containerStyle计算属性生成:
const containerStyle = computed(() => { if (props.height !== 'auto') { return { height: props.height } } return { height: `${containerHeight.value}px`, overflow: 'hidden', } })也就是说,非auto时直接把height字符串当作 CSS 高度使用(因此支持200px、50%等任意合法 CSS 值);auto时则使用内部维护的containerHeight数值。这个数值由激活项通过setContainerHeight上报:在 use-carousel-item.ts 中,当某个 item 变为激活项时,会把自身 DOM 的offsetHeight传给父级容器。
注意:切换轮播项时容器高度是平滑过渡的,切换动画完成前容器会临时设置为overflow: hidden以防内容溢出。这是一个很实用的自适应特性,非常适合「每个轮播项高度不同」的场景。参考示例 auto-height.vue,三个 item 分别设置100px / 200px / 300px高度:
<el-carousel height="auto" autoplay> <el-carousel-item style="height: 100px">...</el-carousel-item> <el-carousel-item style="height: 200px">...</el-carousel-item> <el-carousel-item style="height: 300px">...</el-carousel-item> </el-carousel>2.2 initial-index 与 setActiveItem:定位初始与指定轮播项
initial-index指定初始激活项的索引,默认0,从 0 开始计数。在onMounted阶段,容器会监听子项列表items的变化,一旦子项注册完成就调用setActiveItem(props.initialIndex)完成首屏定位(见 use-carousel.ts)。
setActiveItem是组件对外暴露的方法,接受数字索引或el-carousel-item的name字符串。其实现(use-carousel.ts)有几个值得注意的边界处理:
- 若传入字符串,则按
name匹配子项,找不到匹配项时Number('xxx')为NaN,此时会通过debugWarn输出警告「index must be integer.」并直接返回; - 传入索引必须是整数(
index !== Math.floor(index)会告警); - 索引越界时,依据
loop决定行为:loop为true时,负数回到最后一项、超出范围回到第一项;loop为false时分别钳制到0或最后一项。
2.3 trigger、indicator-position:指示器触发方式与位置
trigger(默认hover):指示器悬停即切换;设为click后仅在点击时切换。对应源码 handleIndicatorHover:只有props.trigger === 'hover'且目标索引不等于当前索引时才会切换;而handleIndicatorClick则无条件直接设置索引。indicator-position(默认''):''为默认(指示器显示在轮播内部底部);outside将指示器移到轮播外部;none隐藏指示器。
此外还有两个细节值得注意。其一,指示器是否显示文字标签取决于子项的label属性:在 carousel.vue 中,只要有一个子项设置了非空label,hasLabel为true,所有指示器都会渲染label文本。其二,当方向为垂直且indicator-position为outside时,容器会额外追加is-vertical-outside修饰类,用于 flex 布局调整(见 carousel.vue)。
2.4 arrow:箭头显示时机
arrow支持三个值,默认hover:
hover:鼠标悬停到轮播区域时显示箭头;always:箭头常驻显示;never:永不显示箭头。
源码中有两点边界行为需要了解。第一,arrowDisplay计算属性(use-carousel.ts)规定:当arrow === 'never'或方向为垂直时,箭头一律不渲染(垂直轮播没有左右箭头)。第二,箭头按钮的显示还受loop影响(carousel.vue):只有loop || activeIndex > 0时才显示左箭头,只有loop || activeIndex < items.length - 1时才显示右箭头——即非循环模式下到达首尾时对应方向的箭头会自动隐藏。箭头点击通过throttledArrowClick做了 300ms 的节流防抖处理(THROTTLE_TIME = 300),避免快速连续点击引发动画错乱。
2.5 autoplay、interval、pause-on-hover、loop:播放与循环控制
这是轮播组件的「发动机」部分,全部实现在 use-carousel.ts 中:
autoplay(默认true):是否自动播放。容器监听该属性,开启时startTimer()启动定时器,关闭时pauseTimer()清除定时器(见 L268-L273)。interval(默认3000):自动播放间隔(毫秒)。startTimer中有一个隐蔽的边界:当interval <= 0时不会启动定时器(见 L103-L106)。监听interval变化时会重置定时器(L281-L286)。pause-on-hover(默认true):鼠标移入轮播区域暂停播放、移出恢复。对应handleMouseEnter/handleMouseLeave(L171-L181):进入时置hover为true并暂停;离开时恢复并重新启动。注意resetTimer(L217-L220)的逻辑:切换轮播项后会先暂停,若pauseOnHover为 false 或当前未悬停,则重新启动定时器。loop(默认true):是否循环。playSlides(L108-L114)中,当前项已是最后一项时:loop为true则回到索引 0,否则停在最后一项不再前进。
const playSlides = () => { if (activeIndex.value < items.value.length - 1) { activeIndex.value = activeIndex.value + 1 } else if (props.loop) { activeIndex.value = 0 } }2.6 direction:水平与垂直布局
direction默认horizontal,设为vertical后轮播沿垂直方向运动,指示器显示在右侧。这一方向的差异贯穿整个实现:
- 轮播项的位移计算在 use-carousel-item.ts:水平方向取
root.offsetWidth,垂直方向取root.offsetHeight,位移量 = 距离 × (索引差); - 样式层面,carousel-item.vue 根据方向选择
translateX或translateY; - 如前述,垂直模式下左右箭头不渲染。
三、六大使用场景示例
以下场景均来自官方文档(对应 docs/examples/carousel 目录),可根据实际需求直接套用。
3.1 指示器移出轮播(Indicators)
indicator-position决定指示器位置。默认在轮播内部,设为outside移出到外部,设为none隐藏:
<el-carousel indicator-position="outside"> <el-carousel-item v-for="item in 4" :key="item"> <h3>{{ item }}</h3> </el-carousel-item> </el-carousel>参见 indicator.vue。
3.2 箭头常驻或隐藏(Arrows)
<el-carousel arrow="always"> <!-- 箭头常驻 --> </el-carousel>参见 arrows.vue。
3.3 卡片模式(Card mode)
当页面宽度充裕但高度受限时,可以开启卡片模式:
<el-carousel type="card" height="200px"> <el-carousel-item v-for="item in 6" :key="item"> <h3>{{ item }}</h3> </el-carousel-item> </el-carousel>参见 card.vue。卡片模式与普通模式的最大区别在于:点击两侧的部分可见卡片会直接切换到该卡片(普通模式下两侧内容不可点击)。这一行为由 carousel-item.vue 上的@click="handleItemClick"实现,对应的handleItemClick(use-carousel-item.ts)在卡片模式下找到当前实例索引并调用父级的setActiveItem。
卡片模式的布局通过calcCardTranslate(L64-L76)完成,其核心公式为:
// 在舞台内(激活项及左右相邻项): translate = (parentWidth * ((2 - cardScale) * (index - activeIndex) + 1)) / 4 // 不在舞台内且位于激活项左侧: translate = (-(1 + cardScale) * parentWidth) / 4 // 不在舞台内且位于激活项右侧: translate = ((3 + cardScale) * parentWidth) / 4配合inStage(索引差绝对值 ≤ 1 即为在舞台内)判断,实现「中间大、两侧小且层叠」的经典卡片效果。非激活项还会附加半透明遮罩层(carousel-item.vue),激活项scale为 1、非激活项scale为cardScale。
3.4 运动模糊(Motion blur,^2.6.0)
motion-blur(默认false)为轮播切换过程注入动态模糊,让动画更有速度感与流畅感。官方示例 motion-blur.vue 同时演示了水平与垂直两种效果:
<el-carousel height="200px" motion-blur> <el-carousel-item v-for="item in 4" :key="item">...</el-carousel-item> </el-carousel> <el-carousel height="200px" direction="vertical" motion-blur :autoplay="false"> <el-carousel-item v-for="item in 4" :key="item">...</el-carousel-item> </el-carousel>其实现原理是 CSS 滤镜 + 过渡期类名配合:组件模板内嵌入了两个隐藏的 SVGfeGaussianBlur滤镜定义,水平方向stdDeviation="12,0"、垂直方向stdDeviation="0,10"(见 carousel.vue)。切换时,容器通过@transitionstart/@transitionend(L179-L195)在动画期间为容器动态添加el-transitioning(水平)或el-transitioning-vertical(垂直)类,样式表(theme-chalk 下的 carousel.scss)在这两个类上应用对应的高斯模糊滤镜,动画结束后移除。因此模糊只出现在切换瞬间,静止时画面保持清晰。
3.5 垂直轮播(Vertical)
<el-carousel direction="vertical" height="200px"> <el-carousel-item v-for="item in 4" :key="item">...</el-carousel-item> </el-carousel>参见 vertical.vue。指示器会渲染在右侧。
四、Events、Slots 与 Exposes
4.1 Carousel Events
官方文档仅定义了一个事件:
| 事件名 | 说明 | 类型 |
|---|---|---|
change | 激活轮播项切换时触发,参数为新激活项的索引与旧激活项的索引 | (current: number, prev: number) => void |
该事件定义在 carousel.ts,并带参数类型校验:两个参数都必须是数字。触发位置在 use-carousel.ts:watch(activeIndex)的回调中,且仅在prev > -1(即确实发生过切换,排除初始化那次)时触发。
<el-carousel @change="(current, prev) => console.log(current, prev)"> ... </el-carousel>需要注意:在恰好只有 2 个轮播项的循环模式下,组件内部会用占位副本保证无缝循环(见下文「无缝循环」),此时change事件回调中的索引会先对 2 取模归一化后再抛出,即始终是0或1。
4.2 Slots
| 插槽名 | 说明 | 子标签 |
|---|---|---|
default | 自定义轮播内容 | el-carousel-item |
el-carousel-item本身也有一个default插槽用于自定义内容。使用时子组件必须放在el-carousel的默认插槽内,否则会触发 use-carousel-item.ts 中的debugWarn警告(提示正确的el-carousel > el-carousel-item嵌套结构)。
4.3 Carousel Exposes(实例方法)
通过模板 ref 可以调用以下实例方法:
| 方法 | 说明 | 类型 |
|---|---|---|
activeIndex(^2.7.8) | 当前激活项的索引 | number |
setActiveItem | 手动切换轮播项,传入从 0 开始的索引或对应el-carousel-item的name | (index: string \| number) => void |
prev | 切换到上一个轮播项 | () => void |
next | 切换到下一个轮播项 | () => void |
setActiveItem的完整边界行为已在 2.2 节详述。prev/next的实现非常简洁(use-carousel.ts),本质是setActiveItem(activeIndex ± 1),因此循环边界行为也由loop决定。
<script setup lang="ts"> import { ref } from 'vue' import type { CarouselInstance } from 'element-plus' const carouselRef = ref<CarouselInstance>() const switchTo = (i: number) => carouselRef.value?.setActiveItem(i) </script> <template> <el-carousel ref="carouselRef"> <el-carousel-item v-for="item in 4" :key="item">...</el-carousel-item> </el-carousel> <button @click="switchTo(2)">跳到第 3 张</button> <button @click="carouselRef?.prev()">上一张</button> <button @click="carouselRef?.next()">下一张</button> </template>实例类型CarouselInstance定义在 instance.ts。注意activeIndex暴露的是exposeActiveIndex——在 2 项循环模式下会先对 2 取模(use-carousel.ts),保证外部读到的始终是「真实可见项」的索引。
五、Carousel-Item API
5.1 Attributes
| 参数名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
name | 轮播项名称,可用于setActiveItem | string | '' |
label | 对应指示器上显示的文字内容 | string \| number | '' |
name的用途:当轮播项数量或顺序是动态的时,用数字索引定位不可靠,此时可给每个 item 命名,再通过carouselRef.setActiveItem('slide-a')精确切换。label则让指示器从纯圆点变成带文字的标签,提升可读性(参考 2.3 节hasLabel逻辑)。两个属性都在 carousel-item.ts 中定义。
5.2 Slots
| 插槽名 | 说明 |
|---|---|
default | 自定义轮播项内容 |
六、底层实现剖析
6.1 父子通信:provide / inject 上下文
el-carousel与el-carousel-item通过 Vue 的provide/inject通信。useCarousel在 use-carousel.ts 中通过carouselContextKey(定义于 constants.ts)提供上下文,包含根 DOM 引用、子项列表、卡片模式/垂直方向标识、loop、cardScale以及addItem/removeItem/setActiveItem/setContainerHeight等方法。每个el-carousel-item挂载时通过carouselContext.addItem把自己注册进父容器,卸载时调用removeItem注销——这套「有序子项」机制复用了@element-plus/hooks的useOrderedChildren,保证了子项按 DOM 顺序排列。
6.2 位移与舞台算法
普通模式下,每个轮播项的位移是「容器尺寸 × (自身索引 − 激活索引)」,见calcTranslate(use-carousel-item.ts)。结合itemStyle的translateX/translateY与 CSS transition,切换时旧项与新项相向滑动。
卡片模式采用「舞台(stage)」概念:激活项及其左右相邻项(索引差 ≤ 1)处于舞台内,显示为中间大、两侧小;其余项被推到舞台外(左侧完全移出、右侧待命),实现循环视觉。processIndex(L46-L62)专门处理循环边界:当激活项是最后一项而目标项是第一项(或相反)时,返回偏移后的索引,保证位移方向正确。
6.3 无缝循环与 2 项特判
为保证loop模式下从最后一项切回第一项时方向自然(不出现倒带式的反向滑动),组件在多个层面做了处理:
- 索引偏移:循环模式下非激活项会经过
processIndex重映射索引,从而让「回绕」发生在视觉上正确的方向; - 2 项特判:
PlaceholderItem(use-carousel.ts)在检测到恰好 2 个el-carousel-item且开启loop、非卡片模式时,会复制一份相同的子项渲染(共 4 个),配合isTwoLengthShow控制指示器只显示当前可见的那一组,实现 2 项的无缝循环(此修复对应 issue #12139)。同时exposeActiveIndex与change事件对 2 项模式做了取模归一化,避免暴露「副本」的索引。
6.4 定时器、节流与 ResizeObserver
- 自动播放:
setInterval驱动playSlides,进入时满足interval <= 0 || !autoplay则不启动; - 交互节流:箭头点击(
throttledArrowClick)与指示器悬停(throttledIndicatorHover)均使用 lodash 的throttle,间隔 300ms,防止连点导致动画堆叠; - 尺寸自适应:挂载时创建
useResizeObserver监听容器尺寸变化并调用resetItemPosition重算位移(L301-L303),窗口缩放、容器尺寸变化时布局不会错乱;卸载时pauseTimer并停止观察器,避免内存泄漏。
6.5 测试覆盖
仓库为轮播组件提供了较完整的测试,见 carousel.test.tsx,覆盖了基本渲染、指示器触发、自动播放、setActiveItem、prev/next、卡片模式点击切换、事件触发参数等关键行为,可作为理解组件契约的补充依据。若在仓库根目录执行测试,可通过 vitest 指定该文件运行(具体命令以仓库 package.json 中 scripts 为准)。
七、总结
el-carousel是 Element Plus 中开箱即用且可定制性很强的展示类组件。把握以下几个要点即可应对绝大多数场景:
- 骨架:
el-carousel包裹若干el-carousel-item,内容完全自定义; - 外观控制:
height(含auto自适应)、direction(水平/垂直)、type="card"+card-scale(卡片模式)、indicator-position、arrow; - 行为控制:
autoplay/interval/pause-on-hover/loop/trigger/initial-index; - 交互增强:
motion-blur运动模糊(^2.6.0),change事件监听切换,setActiveItem/prev/next实例方法实现外部控制; - 进阶玩法:给
el-carousel-item设置name用于稳定定位,设置label让指示器显示文字标签。
源码层面,其核心逻辑集中在 use-carousel.ts 与 use-carousel-item.ts 两个组合式函数中:前者负责定时器、索引管理、子项注册与上下文提供,后者负责位移计算、舞台判断与卡片布局。理解这两份代码,也就掌握了整个组件的运行机制。
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考