uniapp 3端轮播

cpp 复制代码
<template>
	<view class="swiper-card-box" :style="{ height: pageHeight + 'rpx' }">
		<view class="swiper-card-container" @touchstart="handleTouchStart" @touchend="handleTouchEnd" @touchcancel="handleTouchEnd">
			<view class="swiper-card-item" v-for="(item, index) in loopBannerList" @click="onCardClick(index)" :key="index" :style="cardStyles[index]">
				<image :src="item" mode="aspectFill" class="swiper-card-image" />
			</view>
		</view>
	</view>
</template>

<script>
export default {
	name: 'SwiperCard',
	props: {
		pageHeight: {
			type: Number,
			default: 920 // rpx
		},
		banners: {
			type: Array,
			default: () => []
		},
		rotateDeg: {
			type: Number,
			default: 14
		},
		flyDistance: {
			type: Number,
			default: 120 // 单位 vw
		},
		cardWidth: {
			type: Number,
			default: 568 // rpx
		},
		cardHeight: {
			type: Number,
			default: 820 // rpx
		},
		// ✅ 新增:自动轮播相关配置
		autoPlay: { type: Boolean, default: true }, // 是否自动播放
		autoPlayInterval: { type: Number, default: 3000 } // 自动播放间隔 ms
	},

	data() {
		return {
			loopBannerList: [],
			cardStyles: [],
			currentIndex: 0,
			oldIndex: 0,
			startX: 0,
			startY: 0,
			isAnimating: false,
			slideDirection: '',
			allowNewCardTop: false,
			autoTimer: null // ✅ 自动播放定时器
		};
	},

	mounted() {
		this.initLoopList();
		this.updateCardStyles();
		this.startAutoPlay(); // ✅ 启动自动轮播
	},
	beforeDestroy() {
		this.stopAutoPlay(); // ✅ 组件销毁前清理
	},

	methods: {
		initLoopList() {
			console.log(this.banners.length, '>>>>');
			if (this.banners.length === 0) return;
			this.loopBannerList = [...this.banners.slice(-1), ...this.banners, ...this.banners.slice(0, 1)];
			this.currentIndex = 1;
			this.oldIndex = this.currentIndex;
		},

		// ✅ 自动轮播定时器
		startAutoPlay() {
			if (!this.autoPlay || this.banners.length <= 1) return;
			this.stopAutoPlay();
			this.autoTimer = setInterval(() => {
				if (!this.isAnimating) {
					this.slideDirection = 'left';
					this.triggerSlide();
				}
			}, this.autoPlayInterval);
		},
		stopAutoPlay() {
			if (this.autoTimer) {
				clearInterval(this.autoTimer);
				this.autoTimer = null;
			}
		},

		// ✅ 封装触发动画逻辑(原滑动复用)
		triggerSlide(direction = this.slideDirection) {
			if (this.isAnimating) return;
			const len = this.banners.length;

			this.isAnimating = true;
			this.oldIndex = this.currentIndex;
			this.slideDirection = direction;
			this.currentIndex += direction === 'left' ? 1 : -1;
			this.updateCardStyles();

			setTimeout(() => {
				this.allowNewCardTop = true;
				this.updateCardStyles();
			}, 300);

			setTimeout(() => {
				this.currentIndex = ((((this.currentIndex - 1) % len) + len) % len) + 1;
				this.isAnimating = false;
				this.allowNewCardTop = false;
				this.updateCardStyles();
				this.$emit('change', this.currentIndex - 1);
			}, 800);
		},

		onCardClick(index) {
			// ✅ 当前展示的卡片才允许点击
			if (index === this.currentIndex) {
				const realIndex = (this.currentIndex - 1 + this.banners.length) % this.banners.length;
				this.$emit('click', realIndex); // 向父组件传当前 index
			}
		},
		updateCardStyles() {
			this.cardStyles = this.loopBannerList.map((item, index) => {
				const diff = index - this.currentIndex;
				let transform = 'rotate(0deg) translateX(0rpx) translateY(0rpx)';
				let opacity = 1;
				let zIndex = 0;
				let transition = 'none';

				// 飞出动画
				if (this.isAnimating && index === this.oldIndex) {
					transition = 'all 0.4s ease';
					zIndex = this.allowNewCardTop ? 3 : 9;
					opacity = 0;

					if (this.slideDirection === 'left') {
						transform = `rotate(-${this.rotateDeg}deg) translateX(-${this.flyDistance * 7.5}rpx) translateY(-${(this.flyDistance / 4) * 7.5}rpx)`;
					} else if (this.slideDirection === 'right') {
						transform = `rotate(${this.rotateDeg}deg) translateX(${this.flyDistance * 7.5}rpx) translateY(-${(this.flyDistance / 4) * 7.5}rpx)`;
					}
				}
				// 堆叠三张
				else if (Math.abs(diff) <= 1) {
					transition = this.isAnimating ? 'all 0.25s ease' : 'none';
					opacity = diff === 0 ? 1 : 0.8;

					if (diff === 0) {
						zIndex = this.allowNewCardTop ? 7 : 8;
						transform = 'rotate(0deg) translateX(0rpx) translateY(0rpx)';
					} else if (diff === -1) {
						transform = `rotate(-${this.rotateDeg / 3}deg) translateX(-10rpx) translateY(10rpx)`;
						zIndex = 2;
					} else if (diff === 1) {
						transform = `rotate(${this.rotateDeg / 3}deg) translateX(10rpx) translateY(10rpx)`;
						zIndex = 2;
					}
				}
				// 隐藏备用卡
				else {
					opacity = 0;
					zIndex = 0;
					if (this.slideDirection === 'left' && index === this.currentIndex + 2) {
						transform = `rotate(${this.rotateDeg / 3}deg) translateX(100rpx) translateY(10rpx)`;
					} else if (this.slideDirection === 'right' && index === this.currentIndex - 2) {
						transform = `rotate(-${this.rotateDeg / 3}deg) translateX(-100rpx) translateY(10rpx)`;
					}
				}

				return `transform:${transform};opacity:${opacity};z-index:${zIndex};transition:${transition};width:${this.cardWidth}rpx;height:${this.cardHeight}rpx;`;
			});
		},

		handleTouchStart(e) {
			this.stopAutoPlay(); // ✅ 手动滑动时暂停自动轮播
			if (this.isAnimating) return;
			this.startX = e.touches[0].clientX;
			this.startY = e.touches[0].clientY; // ✅ 记录Y坐标
			this.oldIndex = this.currentIndex;
			this.allowNewCardTop = false;
		},

		handleTouchEnd(e) {
			if (this.isAnimating) return;

			const endX = e.changedTouches[0].clientX;
			const endY = e.changedTouches[0].clientY;

			const diffX = endX - this.startX;
			const diffY = endY - this.startY; // ✅ 垂直偏移量

			const minSwipeX = 30; // 横向切换最小滑动距离
			const maxSwipeY = 60; // 垂直滑动容忍度(超过就视为上下滑)

			const len = this.banners.length;

			// ✅ 判断:横向滑动明显大于纵向滑动,且距离足够
			if (Math.abs(diffX) > minSwipeX && Math.abs(diffX) > Math.abs(diffY)) {
				this.slideDirection = diffX < 0 ? 'left' : 'right';
				this.currentIndex += diffX < 0 ? 1 : -1;
				this.isAnimating = true;
				this.updateCardStyles();

				setTimeout(() => {
					this.allowNewCardTop = true;
					this.updateCardStyles();
				}, 300);

				setTimeout(() => {
					this.currentIndex = ((((this.currentIndex - 1) % len) + len) % len) + 1;
					this.isAnimating = false;
					this.allowNewCardTop = false;
					this.updateCardStyles();
					this.$emit('change', this.currentIndex - 1);
				}, 800);
			} else {
				// ✅ 否则忽略这次滑动(例如上下滑或滑动太短)
				this.slideDirection = '';
			}
			// ✅ 重新启动自动轮播
			this.startAutoPlay();
		}
	}
};
</script>

<style scoped>
.swiper-card-box {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
}

.swiper-card-container {
	position: relative;
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
}

.swiper-card-item {
	position: absolute;
	border-radius: 25rpx;
	transform-origin: center center;
	background: #fff;
}

.swiper-card-image {
	width: 100%;
	height: 100%;
	border-radius: inherit;
	object-fit: cover;
	box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.25);
}
</style>

使用

cpp 复制代码
<swiper-card
  :banners="[
    '/static/banner1.jpg',
    '/static/banner2.jpg',
    '/static/banner3.jpg'
  ]"
  :autoPlay="true"
  :autoPlayInterval="4000"
  @click="onBannerClick"
  @change="onBannerChange"
/>
相关推荐
醉城夜风~31 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc886 小时前
微软EDGE浏览器功能学习
前端·学习·edge
破z晓6 小时前
javascript 导出excel表
开发语言·javascript·excel