Vue层叠轮播图tantan-stack

梁韬
2023-12-01
首先npm install vue-tantan-stack --save 安装

vue-tantan-stack地址

将包下载到本地,然后把文件放到项目中
在文件中引入组件

stack(:pages='surprise.imgs',:stackinit='stackinit',@isloading='switchLoading',:isShowStack='isShowStack') 
stackinit: {visible: 3, //控制显示数量}
<template lang="pug">
ul.stack(ref='stackWrap')
	li.stack-item(
		v-for='(item, index) in pages',
		v-if='isShowStack',
		:style='[transformIndex(index), transform(index), { width: pageWidth }]',
		@touchmove.stop.capture.prevent='touchmove',
		@touchstart.stop.capture.prevent='touchstart',
		@touchend.stop.capture.prevent='touchend',
		@touchcancel.stop.capture.prevent='touchend',
		@mousedown.stop.capture.prevent='touchstart',
		@mouseup.stop.capture.prevent='touchend',
		@mousemove.stop.capture.prevent='touchmove',
		@mouseout.stop.capture.prevent='touchend',
		@webkit-transition-end='onTransitionEnd(index)',
		@transitionend='onTransitionEnd(index)'
	)
		img(:src='$getFixImg("basic", item)')
</template>
<script>
import detectPrefixes from './detect-prefixes.js';
export default {
	props: {
		stackinit: {
			type: Object,
			default: [],
		},
		pages: {
			type: Array,
			default: {},
		},
		isShowStack: { //控制ref渲染完
			type: Boolean,
			default: false,
		},
	},
	created() {
		if (document.body.clientWidth >= 750) {
			this.pageWidth = '8.12rem';
			this.isPc = true;
		} else {
			this.pageWidth = '4.06rem';
			this.isPc = false;
		}
		this.$nextTick(() => {
			this.$emit('isloading');
		});
	},
	mounted() {},
	data() {
		return {
			pageWidth: '100%',
			isPc: false,
			basicdata: {
				start: {},
				end: {},
			},
			temporaryData: {
				prefixes: detectPrefixes(),
				offsetY: '',
				poswidth: 0,
				posheight: 0,
				lastPosWidth: '',
				lastPosHeight: '',
				lastZindex: '',
				rotate: 0,
				lastRotate: 0,
				visible: this.stackinit.visible || 3,
				tracking: false,
				animation: false,
				currentPage: this.stackinit.currentPage || 0,
				opacity: 1,
				lastOpacity: 0,
				swipe: false,
				zIndex: 10,
			},
		};
	},
	computed: {
		// 划出面积比例
		offsetRatio() {
			let width = this.$refs.stackWrap.offsetWidth;
			let height = this.$refs.stackWrap.offsetHeight;
			let offsetWidth = width - Math.abs(this.temporaryData.poswidth);
			let offsetHeight = height - Math.abs(this.temporaryData.posheight);
			let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0;
			return ratio > 1 ? 1 : ratio;
		},
		// 划出宽度比例
		offsetWidthRatio() {
			let width = this.$refs.stackWrap.offsetWidth;
			let offsetWidth = width - Math.abs(this.temporaryData.poswidth);
			let ratio = 1 - offsetWidth / width || 0;
			return ratio;
		},
	},
	mounted() {
		// 绑定事件
		// this.$on('next', () => {
		// 	this.next();
		// });
		// this.$on('prev', () => {
		// 	this.prev();
		// });
		// document.addEventListener('touchmove', (e) => {
		// 	e.preventDefault();
		// });
	},
	methods: {
		touchstart(e) {
			if (this.temporaryData.tracking) {
				return;
			}
			// 是否为touch
			if (e.type === 'touchstart') {
				if (e.touches.length > 1) {
					this.temporaryData.tracking = false;
					return;
				} else {
					// 记录起始位置
					this.basicdata.start.t = new Date().getTime();
					this.basicdata.start.x = e.targetTouches[0].clientX;
					this.basicdata.start.y = e.targetTouches[0].clientY;
					this.basicdata.end.x = e.targetTouches[0].clientX;
					this.basicdata.end.y = e.targetTouches[0].clientY;
					// offsetY在touch事件中没有,只能自己计算
					this.temporaryData.offsetY =
						e.targetTouches[0].pageY - this.$refs.stackWrap.offsetParent.offsetTop;
				}
				// pc操作
			} else {
				this.basicdata.start.t = new Date().getTime();
				this.basicdata.start.x = e.clientX;
				this.basicdata.start.y = e.clientY;
				this.basicdata.end.x = e.clientX;
				this.basicdata.end.y = e.clientY;
				this.temporaryData.offsetY = e.offsetY;
			}
			this.temporaryData.tracking = true;
			this.temporaryData.animation = false;
		},
		touchmove(e) {
			// 记录滑动位置
			if (this.temporaryData.tracking && !this.temporaryData.animation) {
				if (e.type === 'touchmove') {
					e.preventDefault();
					this.basicdata.end.x = e.targetTouches[0].clientX;
					this.basicdata.end.y = e.targetTouches[0].clientY;
				} else {
					e.preventDefault();
					this.basicdata.end.x = e.clientX;
					this.basicdata.end.y = e.clientY;
				}
				// 计算滑动值
				this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x;
				this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y;
				let rotateDirection = this.rotateDirection();
				let angleRatio = this.angleRatio();
				this.temporaryData.rotate =
					rotateDirection * this.offsetWidthRatio * 15 * angleRatio;
			}
		},
		touchend(e) {
			this.temporaryData.tracking = false;
			this.temporaryData.animation = true;
			// 滑动结束,触发判断
			// 判断划出面积是否大于0.4
			if (this.offsetRatio >= 0.4) {
				// 计算划出后最终位置
				let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth);
				this.temporaryData.poswidth =
					this.temporaryData.poswidth >= 0
						? this.temporaryData.poswidth + 200
						: this.temporaryData.poswidth - 200;
				this.temporaryData.posheight =
					this.temporaryData.posheight >= 0
						? Math.abs(this.temporaryData.poswidth * ratio)
						: -Math.abs(this.temporaryData.poswidth * ratio);
				this.temporaryData.opacity = 0;
				this.temporaryData.swipe = true;
				this.nextTick();
				// 不满足条件则滑入
			} else {
				this.temporaryData.poswidth = 0;
				this.temporaryData.posheight = 0;
				this.temporaryData.swipe = false;
				this.temporaryData.rotate = 0;
			}
		},
		nextTick() {
			// 记录最终滑动距离
			this.temporaryData.lastPosWidth = this.temporaryData.poswidth;
			this.temporaryData.lastPosHeight = this.temporaryData.posheight;
			this.temporaryData.lastRotate = this.temporaryData.rotate;
			this.temporaryData.lastZindex = 20;
			// 循环currentPage
			this.temporaryData.currentPage =
				this.temporaryData.currentPage === this.pages.length - 1
					? 0
					: this.temporaryData.currentPage + 1;
			// currentPage切换,整体dom进行变化,把第一层滑动置最低
			this.$nextTick(() => {
				this.temporaryData.poswidth = 0;
				this.temporaryData.posheight = 0;
				this.temporaryData.opacity = 1;
				this.temporaryData.rotate = 0;
			});
		},
		onTransitionEnd(index) {
			let lastPage =
				this.temporaryData.currentPage === 0
					? this.pages.length - 1
					: this.temporaryData.currentPage - 1;
			// dom发生变化正在执行的动画滑动序列已经变为上一层
			if (this.temporaryData.swipe && index === lastPage) {
				this.temporaryData.animation = true;
				this.temporaryData.lastPosWidth = 0;
				this.temporaryData.lastPosHeight = 0;
				this.temporaryData.lastOpacity = 0;
				this.temporaryData.lastRotate = 0;
				this.temporaryData.swipe = false;
				this.temporaryData.lastZindex = -1;
			}
		},
		prev() {
			this.temporaryData.tracking = false;
			this.temporaryData.animation = true;
			// 计算划出后最终位置
			let width = this.$refs.stackWrap.offsetWidth;
			this.temporaryData.poswidth = -width;
			this.temporaryData.posheight = 0;
			this.temporaryData.opacity = 0;
			this.temporaryData.rotate = '-3';
			this.temporaryData.swipe = true;
			this.nextTick();
		},
		next() {
			this.temporaryData.tracking = false;
			this.temporaryData.animation = true;
			// 计算划出后最终位置
			let width = this.$refs.stackWrap.offsetWidth;
			this.temporaryData.poswidth = width;
			this.temporaryData.posheight = 0;
			this.temporaryData.opacity = 0;
			this.temporaryData.rotate = '3';
			this.temporaryData.swipe = true;
			this.nextTick();
		},
		rotateDirection() {
			if (this.temporaryData.poswidth <= 0) {
				return -1;
			} else {
				return 1;
			}
		},
		angleRatio() {
			let height = this.$refs.stackWrap.offsetHeight;
			let offsetY = this.temporaryData.offsetY;
			let ratio = -1 * ((2 * offsetY) / height - 1);
			return ratio || 0;
		},
		inStack(index, currentPage) {
			let stack = [];
			let visible = this.temporaryData.visible;
			let length = this.pages.length;
			for (let i = 0; i < visible; i++) {
				if (currentPage + i < length) {
					stack.push(currentPage + i);
				} else {
					stack.push(currentPage + i - length);
				}
			}
			return stack.indexOf(index) >= 0;
		},
		// 非首页样式切换
		transform(index) {
			let currentPage = this.temporaryData.currentPage;
			let length = this.pages.length;
			let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1;
			let style = {};
			let visible = this.temporaryData.visible;
			if (index === this.temporaryData.currentPage) {
				return;
			}
			if (this.inStack(index, currentPage)) {
				let perIndex =
					index - currentPage > 0 ? index - currentPage : index - currentPage + length;
				style['opacity'] = '1';
				style['transform'] =
					'translate3D(' +
					1 * (this.isPc ? 90 : 50) * (perIndex - this.offsetRatio) +
					'px' +
					',0,0) scale(' +
					(perIndex - this.offsetRatio == 1 ? '0.9' : '0.83') +
					')';
				style['transformOrigin'] = 'bottom';
				style['zIndex'] = visible - perIndex;
				if (!this.temporaryData.tracking) {
					style[this.temporaryData.prefixes.transition + 'TimingFunction'] = 'ease';
					style[this.temporaryData.prefixes.transition + 'Duration'] = 300 + 'ms';
				}
			} else if (index === lastPage) {
				style['transform'] =
					'translate3D(' +
					this.temporaryData.lastPosWidth +
					'px' +
					',' +
					this.temporaryData.lastPosHeight +
					'px' +
					',0px) ' +
					'rotate(' +
					this.temporaryData.lastRotate +
					'deg)';
				style['opacity'] = this.temporaryData.lastOpacity;
				style['zIndex'] = this.temporaryData.lastZindex;
				style[this.temporaryData.prefixes.transition + 'TimingFunction'] = 'ease';
				style[this.temporaryData.prefixes.transition + 'Duration'] = 0 + 'ms';
			} else {
				style['zIndex'] = '-1';
				style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')';
			}
			return style;
		},
		// 首页样式切换
		transformIndex(index) {
			if (index === this.temporaryData.currentPage) {
				let style = {};
				style['transform'] =
					'translate3D(' +
					this.temporaryData.poswidth +
					'px' +
					',' +
					this.temporaryData.posheight +
					'px' +
					',0px) ' +
					'rotate(' +
					this.temporaryData.rotate +
					'deg)';
				style['opacity'] = this.temporaryData.opacity;
				style['zIndex'] = 10;
				if (this.temporaryData.animation) {
					style[this.temporaryData.prefixes.transition + 'TimingFunction'] = 'ease';
					style[this.temporaryData.prefixes.transition + 'Duration'] =
						(this.temporaryData.animation ? 300 : 0) + 'ms';
				}
				return style;
			}
		},
	},
};
</script>
<style>
.stack {
	width: 100%;
	height: 100%;
	position: relative;
	perspective: 1000px;
	perspective-origin: 50% 150%;
	-webkit-perspective: 1000px;
	-webkit-perspective-origin: 50% 150%;
	margin: 0;
	padding: 0;
}
.stack-item {
	background: #fff;
	height: 100%;
	width: 100%;
	border-radius: 0.28rem;
	text-align: center;
	overflow: hidden;
	position: absolute;
	opacity: 0;
	display: -webkit-flex;
	display: flex;
	-webkit-flex-direction: column;
	flex-direction: column;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	pointer-events: auto;
}
.stack-item img {
	width: 100%;
	height: 100%;
	display: block;
	pointer-events: none;
}
.stack-container li.move-back {
	/* http://matthewlein.com/ceaser/ */
	-webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.47, 1); /* older webkit */
	-webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.47, 1.515);
	transition-timing-function: cubic-bezier(0.175, 0.885, 0.47, 1.515);
}
</style>

isShowStack 控制模块渲染完毕再展示轮播图,要不然会出现$refs.xxx.offsetWidth 为undefined
然后就完事了,样式可自行调整

 类似资料: