- UI组件
- 前端
【免费下载链接】varlet
A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.
导读:本文围绕 Vue3 移动端组件库 Varlet 中的 Swipe(轮播)组件展开,系统讲解var-swipe与var-swipe-item的配置参数、方法、事件、插槽与样式变量,并结合仓库内组件源码与测试用例,深入剖析循环播放、自动播放、触摸滑动、方向判定等底层实现原理。读完本文,你将能够在自己的项目中快速搭建横/纵向轮播、自定义指示器与导航按钮,并理解其背后的滑动机制。
Swipe 组件是什么
Swipe 是 Varlet 提供的一个基于触摸滑动与 CSS Transform 实现的轮播容器组件,用于"显示可以在屏幕上滚动和滑动的项目集合"。它由两个配套组件组成:
var-swipe:轮播容器,负责触摸事件监听、自动播放、循环逻辑、指示器与导航渲染;var-swipe-item:单个轮播项,每个项占据容器的一屏宽度(或高度)。
在仓库中,两者分别位于 swipe 与 swipe-item 目录,均通过withInstall注册为可全局使用的组件。
基本使用
最简单的轮播只需要在var-swipe中放入若干var-swipe-item:
<template> <var-swipe class="swipe-example"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>使用时有两点需要注意:
var-swipe自身的宽度为 100%,高度需要开发者显式指定(示例中通过.swipe-example { height: 160px }设置),否则轮播容器将没有高度;- 示例中的图片素材
cat.jpg、cat2.jpg、cat3.jpg存放在仓库 public 目录 下,可用于本地演示。
轮播项的尺寸约定
从源码看,SwipeItem.vue 渲染的每个var-swipe-item会通过toSizeUnit(size)将容器宽度(或高度,取决于是否vertical)设置到自身,而 swipeItem.less 中flex-shrink: 0保证每个轮播项在轨道上不会被压缩。也就是说:每个var-swipe-item恰好占满一屏轮播容器的尺寸,这是整个滑动轨道尺寸计算的基础。
禁止循环轮播
默认情况下 Swipe 是循环轮播的(loop默认为true)。如果希望滑到末尾后停止,可以设置:loop="false":
<template> <var-swipe class="swipe-example" :loop="false"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>循环与非循环的边界处理
在 Swipe.vue 中,索引越界由clampIndex处理:循环模式下,索引-1会被折算为length - 1,索引length会被折算为0;非循环模式下则通过clamp将索引限制在[0, length - 1]区间内。测试用例 index.spec.js 中swipe loop用例验证了这一点:当loop: false时连续向左拖动 4 次,change事件最终停留在索引2不再前进。
此外,非循环模式下导航按钮也会联动禁用:从 Swipe.vue 模板可见,上一页按钮在!loop && index === 0时disabled,下一页按钮在!loop && index === length - 1时disabled。
开启自动播放
通过autoplay属性设置自动播放间隔时间(单位为毫秒),轮播将每隔该时间自动切换到下一页:
<template> <var-swipe class="swipe-example" :autoplay="2000"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>自动播放的实现细节
从 Swipe.vue 的startAutoplay源码可以看到三个关键行为:
- 至少两张才自动播放:
if (!autoplay || length.value <= 1) return,单张轮播不会启动定时器; - 递归 setTimeout:每次调用
next()完成切换后,重新调用startAutoplay()布置下一次定时,形成间隔播放链; - 触摸即暂停:
handleTouchstart中会调用stopAutoplay()停止自动播放,松手后在handleTouchend中重新startAutoplay(),避免自动切换与用户手势冲突。
autoplay的类型为string | number,内部通过toNumber统一转换为毫秒数参与定时。测试用例swipe autoplay(autoplay: 100)验证了每隔约 100mschange事件依次触发索引1、2。
垂直轮播
添加vertical属性即可将轮播方向切换为垂直:
<template> <var-swipe class="swipe-example" vertical> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>垂直模式在源码中的体现
垂直模式并不是简单的样式切换,而是贯穿整个滑动链路:
- 轨道尺寸:
resize()中根据vertical决定读取offsetHeight还是offsetWidth,轨道的width/height也随之切换; - 位移方向:
handleTouchmove中轨道位移累加的是moveY而非moveX,transform使用translateY; - 快速滑动方向判定:
handleTouchend中以deltaY < 0(手指向上滑)作为"下一页"的判定; - 指示器与导航布局:垂直模式下指示器改为右侧竖向排列,导航按钮改为上下居中布局(见 swipe.less 中
--indicators-vertical与--navigation-vertical-*相关样式)。
导航按钮
通过navigation属性可开启左右导航箭头。当navigation为hover时,导航按钮会在指针悬浮时显示,适合桌面端场景:
<template> <var-swipe class="swipe-example" navigation> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> <var-swipe class="swipe-example" navigation="hover"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; margin-top: 14px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>导航按钮的实现与扩展
从 Swipe.vue 模板可以看到,导航按钮默认由var-button和var-icon组合渲染:
- 水平模式使用
chevron-left/chevron-right图标,垂直模式使用chevron-up/chevron-down; navigation === 'hover'时,通过v-hover指令维护hovering状态,只有navigation === true || hovering才渲染按钮,并套用进入/离开过渡动画(动画类定义于 swipe.less);- 更关键的是,导航按钮区域(
prev/next插槽)支持完全自定义,可通过插槽传入的index、length、hovering、to、prev、next等插槽属性实现任意形态的翻页控件(详见下文插槽章节)。
监听切换事件
通过@change事件可以监听当前轮播索引的变化,事件参数为当前索引index:
<script setup> import { Snackbar } from '@varlet/ui' </script> <template> <var-swipe class="swipe-example" @change="(index) => Snackbar(String(index))"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } </style>change 事件的触发时机
在 Swipe.vue 中,onChange的回调统一通过call()触发,触发点包括:
- 手势滑动松手后(
handleTouchend):只有prevIndex !== index.value(索引真正变化)时才触发; - 调用
next()/prev()方法时:默认触发,除非传入{ event: false }显式禁止; - 调用
to(idx)跳转时:内部通过逐页调用next/prev实现,且只有到达目标页的那一次才允许触发change(中间页传入event: false)。
类型定义见 props.ts 中的defineListenerProp<(index: number) => void>(),对应的SwipeToOptions.event语义为:event值为false时阻止触发change事件。
自定义指示器
默认指示器是一组小圆点。通过indicator插槽可以完全接管指示器渲染。插槽会收到index(当前索引)、length(总数)和to(跳转方法)等属性:
<template> <var-swipe class="swipe-example"> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat2.jpg"> </var-swipe-item> <var-swipe-item> <img class="swipe-example-image" src="https://varletjs.org/cat3.jpg"> </var-swipe-item> <template #indicator="{ index, length, to }"> <div class="swipe-example-indicators"> <div class="swipe-example-indicator" v-for="(l, idx) in length" :key="l" :class="{'swipe-example-active-indicator': idx === index}" @click="to(idx)" > </div> </div> </template> </var-swipe> </template> <style> .swipe-example { height: 160px; } .swipe-example-image { width: 100%; height: 100%; object-fit: cover; pointer-events: none; } .swipe-example-indicators { position: absolute; display: flex; bottom: 10px; left: 50%; transform: translateX(-50%); } .swipe-example-indicator { width: 8px; height: 8px; background: #fff; opacity: 0.3; margin: 0 4px; transition: opacity 0.3s; } .swipe-example-active-indicator { opacity: 1; } </style>示例中自定义了一个方块指示器:遍历length渲染指示点,当前项(idx === index)高亮,点击任意指示点调用to(idx)跳转。仓库的完整示例可参考 example/index.vue。
API 全览
Props(属性)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
loop | 是否开启循环轮播 | boolean | true |
autoplay | 自动播放间隔时间 (ms) | string | number | - |
duration | 切换过渡时间 (ms) | string | number | 300 |
initial-index | 初始化显示的索引 | string | number | 0 |
indicator | 是否显示指示器 | boolean | true |
indicator-color | 指示器颜色 | string | - |
vertical | 是否开启垂直轮播 | boolean | false |
touchable | 是否可以拖动 | boolean | true |
navigation | 是否显示导航箭头。设置为hover时,指针悬浮会显示导航按钮 | boolean | string | false |
以上默认值与类型与 props.ts 中的定义一一对应。其中duration、autoplay、initialIndex均支持string | number,内部通过toNumber与toSizeUnit处理(见 elements.ts),例如duration="500"与:duration="500"效果一致,过渡时长最终反映在轨道的transition-duration样式上(测试用例swipe duration断言了transition-duration: 500ms)。
方法
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
resize | 产生位置大小变化时可以调用此方法进行重绘 | - | - |
prev | 上一页 | options?: SwipeToOptions | - |
next | 下一页 | options?: SwipeToOptions | - |
to | 跳转到指定下标 | index: number, options?: SwipeToOptions | - |
方法通过组件setup的返回值暴露给模板与外部 ref 调用(见 Swipe.vue 中// expose注释标记的resize、next、prev、to)。其行为要点:
resize()会重新读取容器尺寸、重算trackSize与trackTranslate,并广播给所有轮播项与注册的 resize 监听器;组件在窗口 resize、onActivated(keep-alive 激活)以及轮播项数量变化时都会自动调用它;next()/prev()在length <= 1时直接返回;循环模式下越过边界时会借助fixPosition无缝回绕;to(idx)内部把跳转拆解为逐页的next/prev调用,且只有在最后一步才触发change事件(event: false可全局禁止)。
SwipeToOptions
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
event | 值为false时阻止触发change事件 | boolean | - |
该接口在 props.ts 中定义为{ event?: boolean },在 swipe.d.ts 中同步导出,供 TS 用户直接引用。
事件
| 事件名 | 说明 | 参数 |
|---|---|---|
change | 切换轮播时触发 | index: number轮播索引 |
插槽
Swipe Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
default | 轮播内容 | - |
prev | 上一页按钮 | index: number轮播索引length: number轮播总数hovering: boolean指针是否悬浮to、prev、next: 类型和同名方法一致 |
next | 下一页按钮 | index: number轮播索引length: number轮播总数hovering: boolean指针是否悬浮to、prev、next: 类型和同名方法一致 |
indicator | 指示器内容 | index: number轮播索引length: number轮播总数hovering: boolean指针是否悬浮to、prev、next: 类型和同名方法一致 |
从 Swipe.vue 模板可见,prev/next插槽默认渲染内置导航按钮,而indicator插槽默认渲染内置指示圆点——三者的默认内容都可以被覆盖,插槽属性的完整类型定义在 swipe.d.ts 的SwipeIndicatorData接口中。
SwipeItem Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
default | 轮播项内容 | - |
样式变量
以下为组件使用的 CSS 变量,可以使用 StyleProvider 组件 进行样式定制。
| 变量名 | 默认值 |
|---|---|
--swipe-indicator-color | #fff |
--swipe-indicators-offset | 10px |
--swipe-indicator-offset | 4px |
--swipe-navigation-z-index | 9 |
--swipe-navigation-button-width | 36px |
--swipe-navigation-button-height | 36px |
--swipe-navigation-button-border-radius | 50% |
--swipe-navigation-icon-size | 20px |
--swipe-navigation-prev-left | 8px |
--swipe-navigation-next-right | 8px |
--swipe-navigation-prev-top | 8px |
--swipe-navigation-next-bottom | 8px |
这些变量定义在 swipe.less 的:root中,并分别作用于指示器(颜色、圆点间距、距底位置)与导航按钮(z-index、尺寸、圆角、图标大小、四周偏移)。例如想将指示圆点改为主题色,只需覆盖--swipe-indicator-color。
源码级原理:滑动轨道是如何工作的
理解 Swipe 的底层机制,核心在于理解"轨道 + 位移"模型,可从 Swipe.vue 的渲染与逻辑中还原:
1. 父子组件通过 provide/inject 协作。provide.ts 定义了SwipeProvider(提供size、currentIndex、vertical),swipe-item/provide.ts 通过useParent绑定父级,并断言var-swipe-item必须处于var-swipe内部(否则抛出'SwipeItem', '<var-swipe-item/> must in <var-swipe/>'提示)。这样父组件就能统计length并调度每个子项。
2. 尺寸与位移计算。resize()中size = offsetWidth/offsetHeight,trackSize = size * length,trackTranslate = index * -size,轨道通过translateX/translateY移动,并用transition-duration控制过渡时长(duration属性)。
3. 触摸滑动与方向判定。handleTouchmove中通过isExpectDirection判断手势方向是否与轮播方向一致(水平轮播只响应横向手势),不一致时直接返回,避免与页面纵向滚动冲突;滑动结束后handleTouchend综合位移距离与时间判定目标页:快速滑动(performance.now() - startTime <= 250且位移>= 20)视为翻页,否则根据位移过半情况取最近页。
4. 循环回绕。循环模式下轨道越过首尾时,dispatchSwipeItems会预先将首/尾子项克隆位移放置到轨道两端,松手后fixPosition在关闭过渡动画(lockDuration)的状态下瞬间复位,实现视觉上的无缝循环。
5. 键盘可访问性。Swipe.vue 通过useEventListener监听window的keydown:当某个var-swipe-item处于聚焦状态时,ArrowLeft/ArrowRight分别触发prev()/next(),对应测试用例swipe keyboard Arrow验证了方向键翻页行为。
附:测试与演示参考
若想进一步验证文中所述行为,可以关注:
- 单元测试 swipe/tests/index.spec.js:覆盖了
next/prev/to方法、键盘方向键、loop、autoplay、duration、initial-index、indicator与indicator-color、vertical、touchable、navigation(含hover模式)等全部核心能力; - 移动端演示 example/index.vue:提供了基本使用、禁止循环、自动播放、垂直轮播、导航、监听切换、自定义指示器七个完整的可运行示例;
- 类型声明 types/swipe.d.ts:包含
SwipeProps、SwipeToOptions、SwipeIndicatorData等接口,方便 TS 项目接入。
掌握 Swipe 的配置与原理后,无论是移动端 Banner、图文轮播,还是桌面端带悬浮导航的相册浏览,都可以基于 Varlet 快速落地。
- UI组件
- 前端
【免费下载链接】varlet
A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.
相关推荐
Varlet Swipe 轮播组件完整指南:基础用法、API 参考与源码原理
Varlet Swipe 轮播组件完整指南:基础用法、API 参考与源码原理 轮播(Swipe)是移动端组件库中最常用的交互组件之一,用于在一组内容之间进行横向
UI组件前端rsuite Carousel 轮播组件完全指南:从基础用法到源码级原理剖析
rsuite Carousel 轮播组件完全指南:从基础用法到源码级原理剖析 导读 Carousel 是 rsuite 中用于"以轮播的方式显示一组元素"的核心
前端UI组件Varlet 组件库 Tooltip 提示组件完全指南:从基础用法到源码级原理剖析
Varlet 组件库 Tooltip 提示组件完全指南:从基础用法到源码级原理剖析 导读 Tooltip(提示)是 Varlet 组件库中一个基于 Popper
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考