前言
在 UniApp 小程序开发中,原生 swiper 组件或 uView 的 u-swiper 组件默认仅支持固定间隔 的自动轮播,无法满足 "不同轮播图项设置不同播放时长" 的业务需求(比如广告轮播中,重要广告展示更久,普通广告展示时间更短)。本文基于 uView 的u-swiper组件封装了一个可自定义每张图片播放时长的通用轮播图组件,解决了固定间隔轮播的局限性,同时兼顾了手动滑动交互和内存泄漏问题。
实现思路
- 基于 uView 的
u-swiper搭建基础轮播结构,关闭原生 autoplay,改用自定义定时器控制自动切换; - 通过
props接收父组件传递的轮播数据(包含每张图的自定义播放时长playTime); - 利用
setTimeout定时器实现自动切换,每次切换时根据当前轮播项的playTime重新设置定时器时长; - 监听手动滑动事件(
touchstart/touchend),滑动时暂停自动播放,滑动结束后恢复,避免交互冲突; - 监听轮播数据变化、组件销毁生命周期,做好定时器清理和边界处理(如数据为空的提示),防止内存泄漏。
完整组件代码
javascript
<template>
<div id="myswiper">
<!-- uView轮播组件,关闭原生自动播放,手动控制current -->
<u-swiper
:list="banner"
height="950"
:indicator="false"
:autoplay="false"
:current="currentIndex"
@change="handleSwiperChange"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
></u-swiper>
</div>
</template>
<script>
export default {
name: "Myswiper",
// 接收父组件传递的轮播数据
props: {
banner: {
type: Array,
default: () => [],
required: true // 标记为必传,增强健壮性
},
},
data() {
return {
currentIndex: 0, // 当前轮播图索引
timer: null, // 定时器对象,用于控制自动切换
isTouching: false, // 标记是否正在手动滑动,避免滑动时自动切换
};
},
// 监听banner数据变化(如异步请求加载数据后)
watch: {
banner: {
handler(newVal) {
// 边界处理:轮播数据为空时给出提示
if (newVal.length === 0) {
console.error("【Myswiper组件】轮播图数据为空,请检查props传递!");
return;
}
// 数据更新后重新启动自动播放
this.startAutoPlay();
},
deep: true, // 深度监听数组内容变化(如数组项新增/修改/删除)
immediate: true // 组件初始化时立即执行一次监听逻辑
},
},
// 组件销毁前清理定时器,防止内存泄漏
beforeDestroy() {
this.clearTimer();
},
methods: {
/**
* 启动自动播放(核心方法)
* 每次执行都会清除旧定时器,根据当前轮播项的playTime设置新定时器
*/
startAutoPlay() {
// 先清除旧定时器,避免多个定时器叠加导致切换速度异常
this.clearTimer();
// 获取当前轮播项的播放时长:优先取playTime(秒),默认3秒(3000毫秒)
const currentPlayTime = this.banner[this.currentIndex].playTime || 3;
const currentDuration = currentPlayTime * 1000;
// 设置新定时器,到时间后切换下一张
this.timer = setTimeout(() => {
// 非手动滑动状态下才执行切换
if (!this.isTouching) {
this.nextSlide();
}
}, currentDuration);
},
/**
* 切换到下一张轮播图
* 实现循环切换:索引超出长度后回到0
*/
nextSlide() {
this.currentIndex = (this.currentIndex + 1) % this.banner.length;
// 切换后重新启动自动播放(使用新轮播项的时长)
this.startAutoPlay();
},
/**
* 清除定时器
* 通用方法,避免重复代码
*/
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null; // 清空定时器对象,便于后续判断
}
},
/**
* 监听u-swiper切换事件(手动滑动触发)
* @param {Number} e - 切换后的索引值
*/
handleSwiperChange(e) {
this.currentIndex = e;
// 手动切换后重置自动播放(使用新索引对应的时长)
this.startAutoPlay();
},
/**
* 触摸开始:标记为滑动状态,暂停自动播放
*/
handleTouchStart() {
this.isTouching = true;
this.clearTimer(); // 滑动时清除定时器,避免自动切换干扰手动操作
},
/**
* 触摸结束:取消滑动状态,恢复自动播放
*/
handleTouchEnd() {
this.isTouching = false;
this.startAutoPlay(); // 滑动结束后重新启动自动播放
},
},
};
</script>
<style scoped lang='scss'>
#myswiper {
width: 100%;
// 可根据需求自定义样式,避免样式穿透可使用/deep/
/deep/ .u-swiper__item {
img {
width: 100%;
height: 100%;
object-fit: cover; // 保持图片比例,填充容器
}
}
}
</style>
代码核心解析
1. Props 与数据监听
banner:接收父组件传递的轮播数据,标记为required: true增强健壮性;watch监听banner:deep: true监听数组内容变化,immediate: true保证组件初始化时立即执行逻辑,解决 "异步加载数据后轮播不自动播放" 的问题。
2. 定时器核心逻辑
startAutoPlay:每次执行先清除旧定时器,再根据当前轮播项的playTime(秒转毫秒)设置新定时器;nextSlide:通过取余运算(currentIndex + 1) % banner.length实现循环切换;clearTimer:通用清理方法,避免定时器叠加导致的切换异常。
3. 手动滑动交互处理
touchstart:标记isTouching = true,清除定时器,防止滑动时自动切换;touchend:标记isTouching = false,重启自动播放,恢复轮播逻辑;handleSwiperChange:手动切换后更新索引,重启自动播放,保证切换后使用新项的时长。
4. 生命周期与边界处理
beforeDestroy:清理定时器,防止组件销毁后定时器仍执行导致内存泄漏;- 数据为空判断:给出明确的控制台提示,便于调试。
组件使用示例(父组件)
javascript
<template>
<view class="index-page">
<!-- 引入自定义轮播图组件 -->
<Myswiper :banner="bannerList" />
</view>
</template>
<script>
// 引入组件
import Myswiper from "@/pages/index/component/Myswiper.vue";
export default {
components: {
Myswiper,
},
data() {
return {
// 轮播数据:playTime为每张图的播放时长(单位:秒)
bannerList: [
{
image: "/static/banner1.png", // 图片地址
playTime: 3, // 播放3秒
url: "/pages/detail/detail1" // 可选:跳转链接
},
{
image: "/static/banner2.png",
playTime: 5, // 播放5秒
url: "/pages/detail/detail2"
},
{
image: "/static/banner3.png",
playTime: 2, // 播放2秒
url: "/pages/detail/detail3"
}
],
};
},
// 模拟异步加载轮播数据
onLoad() {
// 模拟接口请求
setTimeout(() => {
this.bannerList = [
{ image: "/static/banner4.png", playTime: 4, url: "/pages/detail/detail4" },
{ image: "/static/banner5.png", playTime: 6, url: "/pages/detail/detail5" },
];
}, 1000);
},
};
</script>