uniapp 3端轮播

请添加图片描述

<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>

使用

<swiper-card
  :banners="[
    '/static/banner1.jpg',
    '/static/banner2.jpg',
    '/static/banner3.jpg'
  ]"
  :autoPlay="true"
  :autoPlayInterval="4000"
  @click="onBannerClick"
  @change="onBannerChange"
/>
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值