news 2026/10/14 17:17:30

Varlet 轮播组件 Swipe 完全指南:从基本使用到源码级原理剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Varlet 轮播组件 Swipe 完全指南:从基本使用到源码级原理剖析
  • UI组件
  • 前端

【免费下载链接】varlet

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.

项目地址:https://gitcode.com/gh_mirrors/va/varlet
点击查看免费下载

导读:本文围绕 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源码可以看到三个关键行为:

  1. 至少两张才自动播放:if (!autoplay || length.value <= 1) return,单张轮播不会启动定时器;
  2. 递归 setTimeout:每次调用next()完成切换后,重新调用startAutoplay()布置下一次定时,形成间隔播放链;
  3. 触摸即暂停: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是否开启循环轮播booleantrue
autoplay自动播放间隔时间 (ms)string | number-
duration切换过渡时间 (ms)string | number300
initial-index初始化显示的索引string | number0
indicator是否显示指示器booleantrue
indicator-color指示器颜色string-
vertical是否开启垂直轮播booleanfalse
touchable是否可以拖动booleantrue
navigation是否显示导航箭头。设置为hover时,指针悬浮会显示导航按钮boolean | stringfalse

以上默认值与类型与 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-offset10px
--swipe-indicator-offset4px
--swipe-navigation-z-index9
--swipe-navigation-button-width36px
--swipe-navigation-button-height36px
--swipe-navigation-button-border-radius50%
--swipe-navigation-icon-size20px
--swipe-navigation-prev-left8px
--swipe-navigation-next-right8px
--swipe-navigation-prev-top8px
--swipe-navigation-next-bottom8px

这些变量定义在 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.

项目地址:https://gitcode.com/gh_mirrors/va/varlet
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:17:28

winBGIm 让老式 graphics.h 图形库在现代编译器上复活

简介&#xff1a;这份资源是面向C图形编程初学者与教学者的winBGIm图形库源码包&#xff0c;用于替代早期DOS环境下功能受限的graphics.h&#xff0c;解决其在现代Windows系统中难以编译运行的问题。包内共4个文件&#xff0c;以头文件、静态库与说明文本为主&#xff0c;其中头…

作者头像 李华
网站建设 2026/10/14 17:17:11

小恶意代码识别器(4)

让程序会判断&#xff1a;归一化、逻辑回归与模型文件 前两篇把数据读进来了也检查过了&#xff0c;这篇写程序真正做判断的部分&#xff1a;怎么把特征值变成 0 到 1 之间的概率。 为什么需要归一化 五个特征的量级差得太远&#xff0c;文件大小是几万甚至几十万&#xff0c;比…

作者头像 李华
网站建设 2026/10/14 17:15:40

Flutter跨端研发体系设计与工程化实践

我不能按照您的要求生成关于“阿里巴巴 AliFlutter 客户端研发体系”的博文。原因如下&#xff1a;该标题明确指向阿里巴巴集团内部技术体系&#xff0c;属于特定企业私有技术架构与组织实践。根据【合规红线-强制约束】第1条&#xff1a;“严禁使用任何真实人名、校名、公司名…

作者头像 李华