<template> <view class="flipcard" id="flipcard" ref="flipcard"> <view class="flipcard-item" v-for="(item, index) in pages" :key="index" :style="[transformIndex(index),transform(index)]" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" @touchcancel="touchend" @transitionend="onTransitionEnd(index,'transitionend')"> <view class="flipcard-item-stack-info"> <view style="display: flex;justify-content: space-between;"> <view class="t3">一起来识谣</view> <view class="t4">已投票</view> </view> <!-- 谣言 --> <view class="yy-content"> <view class="text">谣言:</view> <view class="title"> 支持公众投票,来区分某条信息是否是谣言,支持公众投票,来区分某条众投票,言,支持公众投票, </view> <view class="source"> 来源:新疆网络举报 </view> </view> <!-- 投票 --> <view class="tp"> <view class="real"> <text>真</text> <text>1212312</text> </view> <view class="fail"> <text>142353245</text> <text>假</text> </view> </view> </view> </view> </view> </template> <script> import { getTouchPoint, getElSize } from '@/common/utils.js' export default { props: { pages: { type: Array, default: [] }, stackInit: { type: Object, default: {} }, }, data() { return { el: {}, basicdata: { start: {}, end: {} }, temporaryData: { isStackClick: true, // 多了一个参数 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(e) { let width = this.el.width let height = this.el.height 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.el.offsetWidth let offsetWidth = width - Math.abs(this.temporaryData.poswidth) let ratio = 1 - offsetWidth / width || 0 return ratio } }, created() { this.$nextTick(async e => { this.el = await getElSize('flipcard', this) }) }, methods: { touchstart(e) { if (this.temporaryData.tracking) { return } // 是否为touch if (e.type === 'touchstart') { if (e.touches.length > 1) { this.temporaryData.tracking = false return } else { // 记录起始位置 const point = getTouchPoint(e) // console.log("记录起始位置",point); this.basicdata.start.t = new Date().getTime() this.basicdata.start.x = point.x this.basicdata.start.y = point.y this.basicdata.end.x = point.x this.basicdata.end.y = point.y // offsetY在touch事件中没有,只能自己计算 this.temporaryData.offsetY = point.y - this.el.top // console.log(this.temporaryData.offsetY) } // pc操作 } else { console.log("pc记录起始位置"); uni.showToast({ title: '无效操作', icon: 'none' }); // 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.isStackClick = true // 多了一个参数 this.temporaryData.tracking = true this.temporaryData.animation = false }, touchmove(e) { this.temporaryData.isStackClick = false // 多了一个参数 // 记录滑动位置 if (this.temporaryData.tracking && !this.temporaryData.animation) { const point = getTouchPoint(e) if (e.type === 'touchmove') { e.preventDefault() this.basicdata.end.x = point.x this.basicdata.end.y = point.y } else { e.preventDefault() this.basicdata.end.x = point.x this.basicdata.end.y = point.y } // 计算滑动值 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, index) { console.log(this.temporaryData.isStackClick) if(this.temporaryData.isStackClick) { this.$emit('click', index) // 多了一个参数,触发上层传递事件 this.temporaryData.isStackClick = false } this.temporaryData.isStackClick = true // 多了一个参数 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, log) { 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.el.width 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.el.width 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.el.height 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(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')' style['zIndex'] = visible - perIndex if (!this.temporaryData.tracking) { style['transitionTimingFunction'] = 'ease' style['transitionDuration'] = 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['transitionTimingFunction'] = 'ease' style['transitionDuration'] = 300 + '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['transitionTimingFunction'] = 'ease' style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms' } return style } }, } } </script> <style lang="scss" scoped> @import '../common/variables.scss'; .flipcard { width: 100%; // height: 100%; height: 600rpx; position: relative; // 层叠效果 perspective: 2000rpx; perspective-origin: 50% 150%; -webkit-perspective: 1000px; -webkit-perspective-origin: 50% 150%; margin: 0; padding: 0; &-item { background: linear-gradient(#bed5fe 0%, #eef1fd 92%, #fff 100%); height: 100%; width: 100%; border-radius: 30rpx; 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; padding: 30rpx; /* #ifndef APP-NVUE */ box-sizing: border-box; /* #endif */ box-shadow: 0px 2rpx 30rpx 0px rgba(94, 114, 153, 0.3); &-stack-info { .t3 { padding: 10rpx 30rpx; background-color: #fff; color: #0043ac; font-weight: 600; width: fit-content; border-radius: 16rpx; margin-bottom: 32rpx; } .t4 { padding: 10rpx 30rpx; background-color: #ccc; color: #0043ac; font-weight: 600; width: fit-content; border-radius: 16rpx; margin-bottom: 32rpx; } .yy-content { background-color: red; padding: 10rpx 30rpx; border-radius: 16rpx; background-color: #fff; .text { color: #0043ac; font-weight: 600; } .title { color: #000; font-size: 36rpx; font-weight: 600; margin-top: 32rpx; @include exceeding-line-overflow(3); } .source { margin-top: 20rpx; font-size: 26rpx; color: #c1c1c1; margin-bottom: 20rpx; } } // 投票 .tp { display: flex; width: 100%; margin-top: 32rpx; border-radius: 16rpx; overflow: hidden; color: #fff; background-color: #fff; .real { display: flex; justify-content: space-between; flex: 1; padding: 0 10rpx; // border: 2rpx solid green; border-right: none; background-color: #21a366; } .fail { display: flex; justify-content: space-between; flex: 1; text-align: right; background-color: #cc463d; padding: 0 10rpx; // border: 1rpx solid red; border-left: none; } } .problem { height: 180rpx; overflow: hidden; text-overflow: ellipsis; .problem-text { font-size: 32rpx; color: #333333; line-height: 46rpx; } } .desc { margin-top: 36rpx; .desc-text { font-size: 26rpx; color: #999999; } } .bottom-box { bottom: 30rpx; left: 30rpx; .avatar { width: 80rpx; height: 80rpx; border-radius: 50%; margin-right: 20rpx; } .name { font-size: 28rpx; color: #AAAAAA; } } } } } </style>