SwCarousel
SwCarousel 是基于 transform: translateX 的自动轮播组件,用于 banner / 首图等场景。首尾各克隆一张做无缝循环(切到克隆帧时无动画跳回真实帧,视觉无跳变),内置 autoplay、触摸阈值翻页、dots / 缩略图指示器。
items 制:传 :items 数据 + #default="{ item, index }" 作用域插槽渲染每张(组件按 items 克隆,index 为真实逻辑索引)。
必须写
#default插槽:组件不知道怎么渲染你的item,传了:items却没写#default会渲染空白帧(dev 期 console 警告)。
自由横排滚动用
SwRail:SwCarousel 是「一次一屏 + 自动轮播」;「一行 N 个靠滚动」用SwRail。
基础用法
自动轮播 banner
<SwCarousel :items="banners" aria-label="首页 banner">
<template #default="{ item }">
<SwLink :to="item.link"><SwImage :src="item.img" ratio="16/9" /></SwLink>
</template>
</SwCarousel>
banner 链接(SwCarousel 不内置链接)
SwCarousel 本身不提供任何链接 / 文案 / 按钮——每张 banner 的内容(图片 / 标题 / 按钮 / 跳转地址)全部来自 item 数据,通过 #default="{ item, index }" 作用域插槽渲染。需要可点击的 banner 时按下面两种形态二选一:
- 纯图 banner(整张可点):用
SwLink包住整张 slide(见上方基础用法)。 - 带文案 + 按钮的 banner:把
SwLink/SwButton作为 CTA 放进 slide 内部,不要把整张再套<a>(链接里嵌套可交互元素是非法 HTML)。
图片 + 标题 + CTA 链接(全由 item 驱动)
<!-- 真实 banner:图片 + 标题 + CTA 按钮,链接来自 item.link -->
<SwCarousel :items="banners" aria-label="首页 banner">
<template #default="{ item }">
<div class="banner">
<SwImage :src="item.img" ratio="12/5" />
<h3 class="title">{{ item.title }}</h3>
<SwLink :to="item.link" class="cta">{{ item.cta }}</SwLink>
</div>
</template>
</SwCarousel>
autoplay 与暂停
默认开 autoplay(autoplayDelay 控制每张停留 ms,默认 4000——带文案 banner 够阅读;纯图可传 3000)。桌面 hover、键盘焦点落入、按下拖拽、页面切到后台(visibilitychange)会自动暂停,离开 / 回到前台续播;命中系统「减少动态」(prefers-reduced-motion)时不自动播放。单图或 :autoplay="false" 不开。手动切换(指示器 / 缩略图 / 键盘 / ref 方法)会重置自动轮换计时——刚选中的 slide 享受完整 autoplayDelay,不会被临近的下一次 tick 立即换走。
<SwCarousel :items="banners" :autoplay-delay="5000" :pause-on-hover="true" />
<SwCarousel :items="banners" :autoplay="false" /> <!-- 关闭自动播放 -->
循环与单图降级
loop(默认true):无缝循环。:loop="false"时到首 / 尾停住不绕。- 单图自动降级:
items.length <= 1时不渲克隆、不开 autoplay、不显指示器。
触摸翻页
移动端手指滑动超过 dragThreshold(占容器宽比例,默认 0.15)即翻页,否则回弹。
行为手感:不循环 / initial-index 起始定位 / speed 切换时长 / drag-threshold 拖拽阈值
指示器(dots / 缩略图 / 自定义)
indicator 选 'dots'(默认)/ 'thumbnails' / 'none';位置由 indicatorPlacement 统一控制(dots 与缩略图共用):
| placement | 效果 |
|---|---|
bottom-center(默认) | 底部居中,水平 |
bottom-left / bottom-right | 底部左 / 右对齐 |
left / right | 左 / 右侧竖排 |
outside | banner 下方正常流(不叠加) |
dots 摆位:右侧竖排 / 底部右对齐 / outside
dots 样式
默认 dots 是白色圆点 + 投影(任意背景上都可见,不绑主色);激活点变成变长条并带动画(横排变长 / 竖排变高)。dots 还带放大的点击热区(透明,不改视觉),方便触摸点击。全部样式由 CSS 变量定制:
CSS 变量定制 dots(激活长条 / 主色 / 间距)
<SwCarousel
:items="banners"
style="--sw-carousel-dot-active-color: var(--primary); --sw-carousel-dot-size: 0.625rem"
>
<template #default="{ item }"><SwImage :src="item.img" ratio="16/9" /></template>
</SwCarousel>
缩略图导航
indicator="thumbnails" + :thumbnails(图源数组,长度需与 items 一致)渲染缩略图条。激活缩略图用白色 box-shadow 描边(非硬边框,reflow-safe);非激活项用 brightness 压暗、hover 时回亮。缩略图较多时自动变成可横向滚动的缩略图条,切换时把激活项自动滚进可视区(鼠标滚轮也能滚动条带)。
thumbnailTrigger 控制缩略图的切换触发方式:'click'(默认,点击切换)/ 'hover'(悬停即切换)。
thumbnailLabels 为每个缩略图外层按钮提供完整可访问名称,例如 :thumbnail-labels="movies.map(m => m.title)"。即使视觉片名省略,或 #thumbnail 自定义了内容,按钮仍使用完整标签。标签会去掉首尾空白;未传、空白或缺少的项回退本地化“第 n 张”。请与 thumbnails 保持等长同序;长度不齐时开发环境警告,已有标签照常使用、缺项按序号回退。aria-current、键盘与触摸行为不变。
缩略图导航(底部居中,点击切换)
缩略图导航(悬停即切换)
缩略图默认渲染为裸 <img>(不经 SwImage、无解密能力)。要自定义单张缩略图的渲染——最典型是 .bnc 加密图(裸 img 必裂)——用 #thumbnail 作用域插槽({ thumb, index, active })塞 SwImage,不传插槽时行为与旧版完全一致:
#thumbnail 插槽:SwImage 渲染缩略图(.bnc 加密图同款姿势)
<SwCarousel :items="movies" indicator="thumbnails" :thumbnails="movies.map((m) => m.cover)">
<template #default="{ item }"><SwImage :src="item.cover" alt="" fill /></template>
<!-- 兜底裸 img 不解密;SwImage fill 填满缩略图按钮盒,.bnc 自动解密 -->
<template #thumbnail="{ thumb }"><SwImage :src="thumb" alt="" fill /></template>
</SwCarousel>
<!-- 缩略图导航(右侧竖排,悬停切换) -->
<SwCarousel
:items="list"
indicator="thumbnails"
:thumbnails="list.map(x => x.thumb)"
indicator-placement="right"
thumbnail-trigger="hover"
>
<template #default="{ item }"><SwImage :src="item.img" ratio="16/9" /></template>
</SwCarousel>
自定义指示器
要完全自定义指示器(进度条 / 数字 / 带标题缩略图等)用 #indicator 插槽:
#indicator 插槽:n / total 计数器
<SwCarousel :items="list">
<template #default="{ item }"><img :src="item.img"></template>
<template #indicator="{ current, count, goTo }">
<div class="my-pager">{{ current + 1 }} / {{ count }}</div>
</template>
</SwCarousel>
CSS 变量
指示器(dots / 缩略图)样式全部走 CSS 变量定制,无需覆盖内部选择器:
| 变量 | 默认值 | 说明 |
|---|---|---|
--sw-carousel-indicator-z | 2 | 指示器层级(导航 chrome;兄弟浮层放 1 即不挡圆点) |
--sw-carousel-indicator-offset | 0.75rem | 指示器距边距离(两轴共用) |
--sw-carousel-indicator-inline-offset | 回退 --sw-carousel-indicator-offset | 横轴(left / right)单独距边距离;满宽轮播嵌 max-width 布局时用它把指示器对齐内容容器而非视口。bottom-center(横轴居中)不适用。缩略条(thumbnails)的 max-width 随同一条回退链派生(100% − offset×2) |
--sw-carousel-dot-size | 0.5rem | 圆点尺寸 |
--sw-carousel-dot-active-size | 1.25rem | 激活点的「长条」尺寸(横排变长 / 竖排变高) |
--sw-carousel-dot-gap | 0.5rem | 圆点间距 |
--sw-carousel-dot-color | 白 rgb(255 255 255) | 圆点色 |
--sw-carousel-dot-active-color | 白 | 激活点色 |
--sw-carousel-dot-shadow | 0 0 0.125rem rgb(0 0 0 / 35%) | 圆点投影(白底也可见) |
--sw-carousel-thumb-width | 3.5rem | 缩略图宽 |
--sw-carousel-thumb-height | 2.25rem | 缩略图高 |
--sw-carousel-thumb-gap | 0.5rem | 缩略图间距 |
--sw-carousel-thumb-radius | 0.5rem | 缩略图圆角 |
--sw-carousel-thumb-dim | 0.5 | 非激活缩略图压暗亮度(brightness) |
--sw-carousel-thumb-hover | 0.8 | 缩略图 hover 亮度(brightness) |
--sw-carousel-thumb-active-shadow | 0 0 0 2px rgb(255 255 255 / 90%) | 激活缩略图描边(box-shadow 实现,reflow-safe) |
命令式控制
<script setup>
const carouselRef = ref()
</script>
<template>
<SwCarousel ref="carouselRef" :items="list"><template #default="{ item }">…</template></SwCarousel>
<button @click="carouselRef.next()">下一张</button>
</template>
一屏多卡(slidesPerView)
推荐位 / 创作者位「一行 N 卡自动轮播」形态——slidesPerView 定一屏张数(支持语义断点响应式)、gap 定卡间距;步进恒为 1 张,loop / autoplay / 拖拽 / dots 照常工作。与 SwRail(自由横滚、无 loop/autoplay)互补:要自动轮播选本组件,要自由滚动选 SwRail。
一屏多卡:桌面 3 / 平板 2 / 移动 1 + gap + 自动逐张轮播
Props
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
items | T[] | ✅ | — | 轮播数据源 |
loop | boolean | true | 无缝循环 | |
autoplay | boolean | true | 自动轮播 | |
autoplayDelay | number | 4000 | 每张停留 ms(带文案 banner 够阅读;纯图可传 3000) | |
initialIndex | number | 0 | 初始定位逻辑索引 | |
speed | number | 300 | 切换动画 ms | |
pauseOnHover | boolean | true | hover 与键盘焦点期间暂停(同开关同语义) | |
dragThreshold | number | 0.15 | 拖拽翻页阈值(占容器宽比例) | |
indicator | 'dots' | 'thumbnails' | 'none' | 'dots' | 指示器类型 | |
indicatorPlacement | 'bottom-center' | 'bottom-left' | 'bottom-right' | 'left' | 'right' | 'outside' | 'bottom-center' | 指示器摆位(dots / 缩略图共用;left / right 为竖排) | |
thumbnails | string[] | undefined | indicator='thumbnails' 时的缩略图条图源(长度需与 items 一致) | |
thumbnailLabels | string[] | undefined | 与 thumbnails 等长同序的完整按钮名称;去首尾空白后使用,缺省/空白/缺项回退本地化序号 | |
thumbnailTrigger | 'click' | 'hover' | 'click' | 缩略图切换触发方式('hover' = 悬停即切换) | |
ariaLabel | string | undefined | 传了才输出 role="region" + aria-label | |
track | CarouselTrackProp | undefined | 内置逐张曝光埋点(opt-in,不传零行为),见下「逐张曝光埋点」 | |
slidesPerView | number | { mobile?, tablet?, desktop? } | 1 | 一屏张数(#220):数字=全断点同值;对象=语义断点(mobile <768 / tablet 768–1023 / desktop ≥1024,缺档回退相邻较小档)。多卡时逐张步进、loop 首尾各克隆 max(spv) 张、dots 数 = items 数(每点=最左张)、change 携带最左张逻辑索引。初始化后非响应式(mount 后改本 prop 不生效;断点档位切换是响应式的) | |
gap | string | '0px' | slide 间距(CSS 长度如 '1rem'),配合 slidesPerView 多卡使用。初始化后非响应式(同 slidesPerView) |
逐张曝光埋点(:track)
轮播的曝光语义与 SwLink 的「进视口一次」不同:loop / autoplay 下非当前 slide 被 transform 挪出可视区,slot 内 SwLink 的 IntersectionObserver 判定不可靠——「轮到哪张」只有轮播引擎自己知道,所以由组件级 :track 承担:
<SwCarousel :items="banners" :track="{
exposure: (item, index) => ({
event: 'appPageitemExposure',
payload: {
item_id: (item as Banner).id, item_type: 'banner', item_name: (item as Banner).title,
parent_item_id: '', position: index + 1, page_key: 'home', page_name: '首页',
},
}),
}">
<template #default="{ item }">
<SwLink :to="item.link"><SwImage :src="item.img" ratio="16/9" /></SwLink>
</template>
</SwCarousel>
- 时机:每张轮到且容器进过视口(IO ≥50% 一次性)时上报;实例生命周期内每张只报一次(autoplay 多圈不重复刷曝光);
items引用变化(换内容)后重新计。 exposure(item, index)由业务把轮播 item 映射为埋点指令,返回undefined= 该张不上报。- 点击埋点不内置——slide 内容是业务 slot,点击语义归内容上的 SwLink
:track.click(见上方示例)。 - 上报走
useTracker(tracker gate 关闭时静默 no-op)。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
change | index: number | 当前张变化,携带真实逻辑索引 |
Slots
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
default | { item, index, active } | 每张内容(index 为真实逻辑索引)。必写——缺失会渲染空白帧并 dev 期警告 |
thumbnail | { thumb, index, active } | 自定义单张缩略图内容(如用 SwImage 渲染 .bnc 加密图);不传时兜底为裸 <img>。插槽内容需自带尺寸(SwImage fill / 裸 img 均已适配填满按钮盒) |
indicator | { current, count, goTo } | 自定义指示器(兜底非标场景,如进度条 / 数字 / 带标题缩略图);整条替换会丢内置滚轮横滚 / 激活滚入视野等能力——只想换缩略图渲染用 #thumbnail 即可 |
暴露方法(defineExpose)
| 成员 | 签名 | 说明 |
|---|---|---|
next / prev | () => void | 下一张 / 上一张(loop 无缝) |
goTo | (index: number, opts?: { immediate?: boolean }) => void | 跳到第 N 张(immediate 无动画) |
play / pause | () => void | autoplay 续播 / 暂停 |
current | Readonly<Ref<number>> | 当前真实逻辑索引 |
注意事项
items制 + 必写#default:每张内容走#default="{ item, index }"作用域插槽;index是真实逻辑索引(克隆帧也映射回真实索引)。传了:items却没写#default会渲染空白帧,dev 期 console 警告。- banner 链接来自
item数据:组件不内置链接;纯图 banner 用SwLink包整张,带按钮的 banner 把 CTA(SwLink/SwButton)放进 slide 内部,别把整张再套<a>。 - 自动轮播必静音媒体:若 slide 内是视频,autoplay 场景遵循浏览器策略(静音)。
- 横排滚动用
SwRail:SwCarousel 是一次一屏的轮播,不是自由横排。
active 仅当前真实项为 true,首尾克隆始终 false。autoplay 支持响应式切换;视频准备/播放期间设为 false,失败回退图片后设为 true,恢复时重新计完整间隔。参见 SwVideoBackground。