仿知乎叠加卡片动画切换效果
uni-app 2021-08-24 22:42:37 动画   uni-app   

感谢npro提供的getTouchPoint、getElSize函数方法!

感谢林老板提供的技术指导!

感谢Andy提供的教程贴!https://learnku.com/articles/50593

 

5.gif

4.gif

1、模板文件,这里将其封装为flipcard组件,同时,比较重要的还有css

XML/HTML Code复制内容到剪贴板
  1. <template>  
  2.     <div class="n-full-height n-bg-primary article-answer">  
  3.           
  4.         <!-- >>主页面 -->  
  5.         <div class="article-answer-stack">  
  6.             <flipcard ref="stack" :pages="stackList" :stackInit="stackInit" @click="handleStackClicked"></flipcard>  
  7.         </div>  
  8.           
  9.         <div class="article-answer-btns n-flex-row n-wrap-nowrap n-align-center n-justify-center">  
  10.             <div class="n-flex-column n-align-center" @click="handleStackPrev">  
  11.                 <div class="article-answer-btns-box n-flex-row n-justify-center n-align-center">  
  12.                     <image class="article-answer-btns-img" src="/static/img/icon-buganxingqu.png"></image>  
  13.                 </div>  
  14.                 <text class="article-answer-btns-text">不感兴趣</text>  
  15.             </div>  
  16.   
  17.               
  18.   
  19.             <div class="n-flex-column n-align-center" @click="handleStackNext">  
  20.                 <div class="article-answer-btns-box n-flex-row n-justify-center n-align-center">  
  21.                     <image class="article-answer-btns-img" src="/static/img/icon-shaohou.png"></image>  
  22.                 </div>  
  23.                 <text class="article-answer-btns-text">稍后答</text>  
  24.             </div>  
  25.         </div>  
  26.   
  27.     </div>  
  28. </template>  

 

 

重要的css,卡片的高度和宽度,在这里定义,在组件里面,都是100%:

XML/HTML Code复制内容到剪贴板
  1. <script>  
  2.     import flipcard from './components/flipcard.vue'  
  3.       
  4.     export default {  
  5.         components:{  
  6.             flipcard  
  7.         },  
  8.         data() {  
  9.             return {  
  10.                   
  11.                 stackList:[  
  12.                     {  
  13.                         title:"2021高考作文难吗11111?",  
  14.                         tag: "最近83人提问过这个问题11111",  
  15.                         problem: "关于陈红军壮烈牺牲的报道,陈红军的“发小”王煦辉这几天看了一遍又一遍,从小玩到大的伙伴如今已经天人相隔,让他至今也无法接受。在同学眼中,陈红军打小就是一位品学兼优的好子,就是这个家境贫寒的农村娃,考上了城里的高?",  
  16.                         author: {  
  17.                             id: 1,  
  18.                             name: "一颗棒棒糖",  
  19.                             avatar: "/static/avatar/1.jpg",  
  20.                         },  
  21.                         comment_num: 5160, // 评论数  
  22.                         dig_num: 5160, // 顶数  
  23.                         forward_num: 5160, // 转发数  
  24.                         attention_num: 30, // 关注数  
  25.                     },  
  26.                     {  
  27.                         title:"2021高考作文难吗?22222",  
  28.                         tag: "最近83人提问过这个问题22222",  
  29.                         problem: "关于陈红军壮烈牺牲的报道,陈红军的“发小”王煦辉这几天看了一遍又一遍,从小玩到大的伙伴如今已经天人相隔,让他至今也无法接受。在同学眼中,陈红军打小就是一位品学兼优的好子,就是这个家境贫寒的农村娃,考上了城里的高?",  
  30.                         author: {  
  31.                             id: 1,  
  32.                             name: "两颗棒棒糖",  
  33.                             avatar: "/static/avatar/2.jpg",  
  34.                         },  
  35.                         comment_num: 5160, // 评论数  
  36.                         dig_num: 5160, // 顶数  
  37.                         forward_num: 5160, // 转发数  
  38.                         attention_num: 30, // 关注数  
  39.                     },  
  40.                     {  
  41.                         title:"2021高考作文难吗?33333",  
  42.                         tag: "最近83人提问过这个问题33333",  
  43.                         problem: "关于陈红军壮烈牺牲的报道,陈红军的“发小”王煦辉这几天看了一遍又一遍,从小玩到大的伙伴如今已经天人相隔,让他至今也无法接受。在同学眼中,陈红军打小就是一位品学兼优的好子,就是这个家境贫寒的农村娃,考上了城里的高?",  
  44.                         author: {  
  45.                             id: 1,  
  46.                             name: "三颗棒棒糖",  
  47.                             avatar: "/static/avatar/4.jpg",  
  48.                         },  
  49.                         comment_num: 5160, // 评论数  
  50.                         dig_num: 5160, // 顶数  
  51.                         forward_num: 5160, // 转发数  
  52.                         attention_num: 30, // 关注数  
  53.                     },  
  54.                     {  
  55.                         title:"2021高考作文难吗?44444",  
  56.                         tag: "最近83人提问过这个问题44444",  
  57.                         problem: "关于陈红军壮烈牺牲的报道,陈红军的“发小”王煦辉这几天看了一遍又一遍,从小玩到大的伙伴如今已经天人相隔,让他至今也无法接受。在同学眼中,陈红军打小就是一位品学兼优的好子,就是这个家境贫寒的农村娃,考上了城里的高?",  
  58.                         author: {  
  59.                             id: 1,  
  60.                             name: "四颗棒棒糖",  
  61.                             avatar: "/static/avatar/5.jpg",  
  62.                         },  
  63.                         comment_num: 5160, // 评论数  
  64.                         dig_num: 5160, // 顶数  
  65.                         forward_num: 5160, // 转发数  
  66.                         attention_num: 30, // 关注数  
  67.                     }  
  68.                 ],  
  69.                 stackInit: {  
  70.                     currentPage:0,  
  71.                     visible: 3  
  72.                 }  
  73.             }  
  74.         },  
  75.         methods: {  
  76.               
  77.             handleStackPrev(){  
  78.                 this.$refs["stack"].prev()  
  79.             },  
  80.             handleStackNext(){  
  81.                 this.$refs["stack"].next()  
  82.             }  
  83.         }  
  84.     }  
  85. </script>  
  86.   
  87. <style lang="scss">  
  88.     page {  
  89.         background-color: #477FE7;  
  90.     }  
  91.   
  92.     .article-answer {  
  93.         &-stack{  
  94.             width: 690rpx;  
  95.             height: 795rpx;  
  96.             position: relative;  
  97.             // z-index: 9;  
  98.             list-style: none;  
  99.             pointer-events: none;  
  100.             padding: 30rpx;  
  101.         }  
  102.     }  
  103. </style>  

 

 

2、组件部分,这里我试了一下,兼容H5和APP,我在写当前交互的时候是在APP项目上,H5调试,测试都OK,直接复制就好

XML/HTML Code复制内容到剪贴板
  1. <template>  
  2.     <div class="flipcard" id="flipcard" ref="flipcard">  
  3.         <div class="flipcard-item" v-for="(item, index) in pages" :key="index"  
  4.             :style="[transformIndex(index),transform(index)]" @touchstart="touchstart" @touchmove="touchmove"  
  5.             @touchend="touchend" @touchcancel="touchend" @transitionend="onTransitionEnd(index,'transitionend')">  
  6.             <div class="flipcard-item-stack-info">  
  7.                 <div class="t3"><text class="t3-text">{{item.tag}}</text></div>  
  8.                 <div class="problem">  
  9.                     <text class="problem-text">{{item.problem}}</text>  
  10.                 </div>  
  11.                 <div class="desc n-flex-row n-wrap-nowrap n-align-center">  
  12.                     <text class="desc-text">{{item.attention_num}}人关注</text>  
  13.                     <text class="desc-text">·</text>  
  14.                     <text class="desc-text">{{item.comment_num}}人回答</text>  
  15.                 </div>  
  16.                 <div class="n-flex-row n-wrap-nowrap n-align-center n-position-absolute bottom-box">  
  17.                     <image class="avatar" :src="item.author.avatar" mode="aspectFill"></image>  
  18.                     <text class="name">{{item.author.name}}</text>  
  19.                 </div>  
  20.             </div>  
  21.         </div>  
  22.     </div>  
  23. </template>  
  24.   
  25. <script>  
  26.     /**  
  27.      * @Desc     Vue仿探探/知乎|Tinder卡片滑动FlipCard  
  28.      * @Time     by 2021-08-24  
  29.      * @About    Q:398927951  
  30.      */  
  31.     import {  
  32.         getTouchPoint,  
  33.         getElSize  
  34.     } from '@/nPro/utils/element.js'  
  35.   
  36.     export default {  
  37.         props: {  
  38.             pages: {  
  39.                 type: Array,  
  40.                 default: []  
  41.             },  
  42.             stackInit: {  
  43.                 type: Object,  
  44.                 default: {}  
  45.             },  
  46.         },  
  47.         data() {  
  48.             return {  
  49.                 el: {  
  50.   
  51.                 },  
  52.                 basicdata: {  
  53.                     start: {},  
  54.                     end: {}  
  55.                 },  
  56.                 temporaryData: {  
  57.                     offsetY: '',  
  58.                     poswidth: 0,  
  59.                     posheight: 0,  
  60.                     lastPosWidth: '',  
  61.                     lastPosHeight: '',  
  62.                     lastZindex: '',  
  63.                     rotate: 0,  
  64.                     lastRotate: 0,  
  65.                     visible: this.stackInit.visible || 3, // 展示几个  
  66.                     tracking: false,  
  67.                     animation: false,  
  68.                     currentPage: this.stackInit.currentPage || 0, // 当前第几个  
  69.                     opacity: 1,  
  70.                     lastOpacity: 0,  
  71.                     swipe: false,  
  72.                     zIndex: 10  
  73.                 }  
  74.             }  
  75.         },  
  76.         computed: {  
  77.             // 划出面积比例  
  78.             offsetRatio(e) {  
  79.                 let width = this.el.width  
  80.                 let height = this.el.height  
  81.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)  
  82.                 let offsetHeight = height - Math.abs(this.temporaryData.posheight)  
  83.                 let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0  
  84.                 return ratio > 1 ? 1 : ratio  
  85.             },  
  86.             // 划出宽度比例  
  87.             offsetWidthRatio() {  
  88.                 let width = this.el.offsetWidth  
  89.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)  
  90.                 let ratio = 1 - offsetWidth / width || 0  
  91.                 return ratio  
  92.             }  
  93.         },  
  94.         created() {  
  95.             this.$nextTick(async e => {  
  96.                 this.el = await getElSize('flipcard', this)  
  97.             })  
  98.         },  
  99.         methods: {  
  100.             touchstart(e) {  
  101.                 if (this.temporaryData.tracking) {  
  102.                     return  
  103.                 }  
  104.                 // 是否为touch  
  105.                 if (e.type === 'touchstart') {  
  106.                     if (e.touches.length > 1) {  
  107.                         this.temporaryData.tracking = false  
  108.                         return  
  109.                     } else {  
  110.                         // 记录起始位置  
  111.                         const point = getTouchPoint(e)  
  112.   
  113.                         // console.log("记录起始位置",point);  
  114.                         this.basicdata.start.t = new Date().getTime()  
  115.                         this.basicdata.start.x = point.x  
  116.                         this.basicdata.start.y = point.y  
  117.                         this.basicdata.end.x = point.x  
  118.                         this.basicdata.end.y = point.y  
  119.                         // offsetY在touch事件中没有,只能自己计算  
  120.                         this.temporaryData.offsetY = point.y - this.el.top  
  121.                         // console.log(this.temporaryData.offsetY)  
  122.                     }  
  123.                     // pc操作  
  124.                 } else {  
  125.                     console.log("pc记录起始位置");  
  126.                     uni.showToast({  
  127.                         title: '无效操作',  
  128.                         icon: 'none'  
  129.                     });  
  130.                     // this.basicdata.start.t = new Date().getTime()  
  131.                     // this.basicdata.start.x = e.clientX  
  132.                     // this.basicdata.start.y = e.clientY  
  133.                     // this.basicdata.end.x = e.clientX  
  134.                     // this.basicdata.end.y = e.clientY  
  135.                     // this.temporaryData.offsetY = e.offsetY  
  136.                 }  
  137.                 this.temporaryData.tracking = true  
  138.                 this.temporaryData.animation = false  
  139.             },  
  140.             touchmove(e) {  
  141.                 // 记录滑动位置  
  142.                 if (this.temporaryData.tracking && !this.temporaryData.animation) {  
  143.                     const point = getTouchPoint(e)  
  144.                     if (e.type === 'touchmove') {  
  145.                         e.preventDefault()  
  146.                         this.basicdata.end.x = point.x  
  147.                         this.basicdata.end.y = point.y  
  148.                     } else {  
  149.                         e.preventDefault()  
  150.                         this.basicdata.end.x = point.x  
  151.                         this.basicdata.end.y = point.y  
  152.                     }  
  153.                     // 计算滑动值  
  154.                     thisthis.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x  
  155.                     thisthis.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y  
  156.                     let rotateDirection = this.rotateDirection()  
  157.                     let angleRatio = this.angleRatio()  
  158.                     this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio  
  159.                 }  
  160.             },  
  161.             touchend(e, index) {  
  162.                 this.temporaryData.tracking = false  
  163.                 this.temporaryData.animation = true  
  164.                 // 滑动结束,触发判断  
  165.                 // 判断划出面积是否大于0.4  
  166.                 if (this.offsetRatio >= 0.4) {  
  167.                     // 计算划出后最终位置  
  168.                     let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)  
  169.                     thisthis.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 :  
  170.                         this.temporaryData.poswidth - 200  
  171.                     thisthis.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData  
  172.                         .poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)  
  173.                     this.temporaryData.opacity = 0  
  174.                     this.temporaryData.swipe = true  
  175.                     this.nextTick()  
  176.                     // 不满足条件则滑入  
  177.                 } else {  
  178.                     this.temporaryData.poswidth = 0  
  179.                     this.temporaryData.posheight = 0  
  180.                     this.temporaryData.swipe = false  
  181.                     this.temporaryData.rotate = 0  
  182.                 }  
  183.             },  
  184.             nextTick() {  
  185.                 // 记录最终滑动距离  
  186.                 thisthis.temporaryData.lastPosWidth = this.temporaryData.poswidth  
  187.                 thisthis.temporaryData.lastPosHeight = this.temporaryData.posheight  
  188.                 thisthis.temporaryData.lastRotate = this.temporaryData.rotate  
  189.                 this.temporaryData.lastZindex = 20  
  190.                 // 循环currentPage  
  191.                 thisthis.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this  
  192.                     .temporaryData.currentPage + 1  
  193.                 // currentPage切换,整体dom进行变化,把第一层滑动置最低  
  194.                 this.$nextTick(() => {  
  195.                     this.temporaryData.poswidth = 0  
  196.                     this.temporaryData.posheight = 0  
  197.                     this.temporaryData.opacity = 1  
  198.                     this.temporaryData.rotate = 0  
  199.                 })  
  200.             },  
  201.             onTransitionEnd(index, log) {  
  202.                 let lastPage = this.temporaryData.currentPage === 0 ? this.pages.length - 1 : this.temporaryData  
  203.                     .currentPage - 1  
  204.                 // dom发生变化正在执行的动画滑动序列已经变为上一层  
  205.                 if (this.temporaryData.swipe && index === lastPage) {  
  206.                     this.temporaryData.animation = true  
  207.                     this.temporaryData.lastPosWidth = 0  
  208.                     this.temporaryData.lastPosHeight = 0  
  209.                     this.temporaryData.lastOpacity = 0  
  210.                     this.temporaryData.lastRotate = 0  
  211.                     this.temporaryData.swipe = false  
  212.                     this.temporaryData.lastZindex = -1  
  213.                 }  
  214.             },  
  215.             prev() {  
  216.                 this.temporaryData.tracking = false  
  217.                 this.temporaryData.animation = true  
  218.                 // 计算划出后最终位置  
  219.                 let width = this.el.width  
  220.                 this.temporaryData.poswidth = -width  
  221.                 this.temporaryData.posheight = 0  
  222.                 this.temporaryData.opacity = 0  
  223.                 this.temporaryData.rotate = '-3'  
  224.                 this.temporaryData.swipe = true  
  225.                 this.nextTick()  
  226.             },  
  227.             next() {  
  228.                 this.temporaryData.tracking = false  
  229.                 this.temporaryData.animation = true  
  230.                 // 计算划出后最终位置  
  231.                 let width = this.el.width  
  232.                 this.temporaryData.poswidth = width  
  233.                 this.temporaryData.posheight = 0  
  234.                 this.temporaryData.opacity = 0  
  235.                 this.temporaryData.rotate = '3'  
  236.                 this.temporaryData.swipe = true  
  237.                 this.nextTick()  
  238.             },  
  239.             rotateDirection() {  
  240.                 if (this.temporaryData.poswidth <= 0) {  
  241.                     return -1  
  242.                 } else {  
  243.                     return 1  
  244.                 }  
  245.             },  
  246.             angleRatio() {  
  247.                 let height = this.el.height  
  248.                 let offsetY = this.temporaryData.offsetY  
  249.                 let ratio = -1 * (2 * offsetY / height - 1)  
  250.                 return ratio || 0  
  251.             },  
  252.             inStack(index, currentPage) {  
  253.                 let stack = []  
  254.                 let visible = this.temporaryData.visible  
  255.                 let length = this.pages.length  
  256.                 for (let i = 0; i < visible; i++) {  
  257.                     if (currentPage + i < length) {  
  258.                         stack.push(currentPage + i)  
  259.                     } else {  
  260.                         stack.push(currentPage + i - length)  
  261.                     }  
  262.                 }  
  263.                 return stack.indexOf(index) >= 0  
  264.             },  
  265.             // 非首页样式切换  
  266.             transform(index) {  
  267.                 let currentPage = this.temporaryData.currentPage  
  268.                 let length = this.pages.length  
  269.                 let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1  
  270.                 let style = {}  
  271.                 let visible = this.temporaryData.visible  
  272.                 if (index === this.temporaryData.currentPage) {  
  273.                     return  
  274.                 }  
  275.                 if (this.inStack(index, currentPage)) {  
  276.                     let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length  
  277.                     style['opacity'] = '1'  
  278.                     style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')'  
  279.                     style['zIndex'] = visible - perIndex  
  280.                       
  281.                     if (!this.temporaryData.tracking) {    
  282.                         style['transitionTimingFunction'] = 'ease'    
  283.                         style['transitionDuration'] = 300 + 'ms'    
  284.                     }  
  285.                 } else if (index === lastPage) {  
  286.                     style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData  
  287.                         .lastPosHeight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.lastRotate + 'deg)'  
  288.                     style['opacity'] = this.temporaryData.lastOpacity  
  289.                     style['zIndex'] = this.temporaryData.lastZindex  
  290.                     style['transitionTimingFunction'] = 'ease'    
  291.                     style['transitionDuration'] = 300 + 'ms'  
  292.                 } else {  
  293.                     style['zIndex'] = '-1'  
  294.                     style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'  
  295.                 }  
  296.                 return style  
  297.             },  
  298.             // 首页样式切换  
  299.             transformIndex(index) {  
  300.                 if (index === this.temporaryData.currentPage) {  
  301.                     let style = {}  
  302.                     style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData  
  303.                         .posheight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.rotate + 'deg)'  
  304.                     style['opacity'] = this.temporaryData.opacity  
  305.                     style['zIndex'] = 10  
  306.                     if (this.temporaryData.animation) {  
  307.                         style['transitionTimingFunction'] = 'ease'    
  308.                         style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'    
  309.                     }  
  310.                     return style  
  311.                 }  
  312.             },  
  313.         }  
  314.     }  
  315. </script>  
  316.   
  317. <style lang="less" scoped>  
  318.     .flipcard {  
  319.         width: 100%;  
  320.         height: 100%;  
  321.         position: relative;  
  322.         perspective: 1000px;  
  323.         perspective-origin: 50% 150%;  
  324.         -webkit-perspective: 1000px;  
  325.         -webkit-perspective-origin: 50% 150%;  
  326.         margin: 0;  
  327.         padding: 0;  
  328.   
  329.         &-item {  
  330.             background: #fff;  
  331.             height: 100%;  
  332.             width: 100%;  
  333.             border-radius: 30rpx;  
  334.             overflow: hidden;  
  335.             position: absolute;  
  336.             opacity: 0;  
  337.             display: -webkit-flex;  
  338.             display: flex;  
  339.             -webkit-flex-direction: column;  
  340.             flex-direction: column;  
  341.             -webkit-touch-callout: none;  
  342.             -webkit-user-select: none;  
  343.             -khtml-user-select: none;  
  344.             -moz-user-select: none;  
  345.             -ms-user-select: none;  
  346.             user-select: none;  
  347.             pointer-events: auto;  
  348.             padding: 30rpx;  
  349.             /* #ifndef APP-NVUE */  
  350.             box-sizing: border-box;  
  351.             /* #endif */  
  352.             box-shadow: 0px 2rpx 30rpx 0px rgba(94, 114, 153, 0.3);  
  353.   
  354.   
  355.             &-stack-info {  
  356.   
  357.                 .t3 {  
  358.                     padding: 0 0 25rpx 0;  
  359.   
  360.                     .t3-text {  
  361.                         font-size: 26rpx;  
  362.                         background-color: #ECECEC;  
  363.                         padding: 8rpx 18rpx;  
  364.                         color: #999999;  
  365.                         border-radius: 10rpx;  
  366.                     }  
  367.                 }  
  368.   
  369.                 .problem {  
  370.                     height: 180rpx;  
  371.                     overflow: hidden;  
  372.                     text-overflow: ellipsis;  
  373.   
  374.                     .problem-text {  
  375.                         font-size: 32rpx;  
  376.                         color: #333333;  
  377.                         line-height: 46rpx;  
  378.                     }  
  379.                 }  
  380.   
  381.                 .desc {  
  382.                     margin-top: 36rpx;  
  383.   
  384.                     .desc-text {  
  385.                         font-size: 26rpx;  
  386.                         color: #999999;  
  387.                     }  
  388.                 }  
  389.   
  390.                 .bottom-box {  
  391.                     bottom: 30rpx;  
  392.                     left: 30rpx;  
  393.   
  394.                     .avatar {  
  395.                         width: 80rpx;  
  396.                         height: 80rpx;  
  397.                         border-radius: 50%;  
  398.                         margin-right: 20rpx;  
  399.                     }  
  400.   
  401.                     .name {  
  402.                         font-size: 28rpx;  
  403.                         color: #AAAAAA;  
  404.                     }  
  405.                 }  
  406.             }  
  407.         }  
  408.     }  
  409. </style>  

 

 

以下是getTouchPoint,getElSize用到的JS部分

JavaScript Code复制内容到剪贴板
  1. // #ifdef APP-NVUE  
  2. const dom = uni.requireNativePlugin("dom")  
  3. // #endif  
  4.   
  5. export function getElSize(name, ins) {  
  6.     return new Promise((res, rej) => {  
  7.         // #ifndef APP-NVUE  
  8.         const el = uni.createSelectorQuery().in(ins).select('#' + name);  
  9.         el.fields({  
  10.             size: true,  
  11.             rect: true  
  12.         }, (data) => {  
  13.             if (data) {  
  14.                 res(data);  
  15.             } else {  
  16.                 rej({})  
  17.             }  
  18.         }).exec();  
  19.         // #endif  
  20.         // #ifdef APP-NVUE  
  21.         let _el = ins.$refs[name][0]  
  22.         if (!_el) {  
  23.             _el = ins.$refs[name]  
  24.         }  
  25.         dom.getComponentRect(_el, (data) => {  
  26.             if (data.result) {  
  27.                 res(data.size)  
  28.             } else {  
  29.                 rej({})  
  30.             }  
  31.         })  
  32.         // #endif  
  33.     })  
  34. }  
  35.   
  36. export function getTouchPoint(e) {  
  37.     if (!e) {  
  38.         return {  
  39.             x: 0,  
  40.             y: 0,  
  41.             sX: 0,  
  42.             sY: 0  
  43.         }  
  44.     }  
  45.     if (e.touches && e.touches[0]) {  
  46.         return {  
  47.             x: e.touches[0].pageX,  
  48.             y: e.touches[0].pageY,  
  49.             sX: e.touches[0].screenX,  
  50.             sY: e.touches[0].screenY  
  51.         }  
  52.     } else if (e.changedTouches && e.changedTouches[0]) {  
  53.         return {  
  54.             x: e.changedTouches[0].pageX,  
  55.             y: e.changedTouches[0].pageY,  
  56.             sX: e.changedTouches[0].screenX,  
  57.             sY: e.changedTouches[0].screenY  
  58.         }  
  59.     } else {  
  60.         return {  
  61.             x: e.clientX,  
  62.             y: e.clientY  
  63.         }  
  64.     }  
  65. }  

 

3、最后一步,页面随着手指滑动,页面不固定,在pages.json给它固定一下:

XML/HTML Code复制内容到剪贴板
  1. {  
  2.     "path": "pages/article/answer",  
  3.     "style": {  
  4.         "navigationBarTitleText": "写回答",  
  5.         "navigationStyle":"custom",  
  6.         "disableScroll":true    // 禁止当前页面滚动/固定页面不滚动  
  7.     }  
  8. }  

 

 

 


 

 

 

 

 

2021.08.25,动画效果:在跟随手指移动的时候,带倾斜效果

6.gif

 

 3D卡片切换的效果,取决于传参的pages数据,是否超过三个!!正好三个就是以下效果

3.gif

 

组件直接复制

XML/HTML Code复制内容到剪贴板
  1. <template>    
  2.     <div class="flipcard" id="flipcard" ref="flipcard">    
  3.         <div class="flipcard-item" v-for="(item, index) in pages" :key="index"    
  4.             :style="[transformIndex(index),transform(index)]" @touchstart="touchstart" @touchmove="touchmove"    
  5.             @touchend="touchend" @touchcancel="touchend" @transitionend="onTransitionEnd(index,'transitionend')">    
  6.             <!-- <img :src="item.author.avatar" /> -->    
  7.             <div class="flipcard-item-stack-info">    
  8.                 <div class="t3"><text class="t3-text">{{item.tag}}</text></div>    
  9.                 <div class="problem">    
  10.                     <text class="problem-text">{{item.problem}}</text>    
  11.                 </div>    
  12.                 <div class="desc n-flex-row n-wrap-nowrap n-align-center">    
  13.                     <text class="desc-text">{{item.attention_num}}人关注</text>    
  14.                     <text class="desc-text">·</text>    
  15.                     <text class="desc-text">{{item.comment_num}}人回答</text>    
  16.                 </div>    
  17.                 <div class="n-flex-row n-wrap-nowrap n-align-center n-position-absolute bottom-box">    
  18.                     <image class="avatar" :src="item.author.avatar" mode="aspectFill"></image>    
  19.                     <text class="name">{{item.author.name}}</text>    
  20.                 </div>    
  21.             </div>    
  22.         </div>    
  23.     </div>    
  24. </template>    
  25.     
  26. <script>    
  27.     /**    
  28.      * @Desc     Vue仿探探|Tinder卡片滑动FlipCard    
  29.      * @Time     by 2021-08-24    
  30.      * @About    Q:398927951    
  31.      */    
  32.     import {    
  33.         getTouchPoint,    
  34.         getElSize    
  35.     } from '@/nPro/utils/element.js'    
  36.     
  37.     export default {    
  38.         props: {    
  39.             pages: {    
  40.                 type: Array,    
  41.                 default: []    
  42.             },  
  43.             stackInit: {  
  44.                 type: Object,    
  45.                 default: {}    
  46.             },  
  47.         },    
  48.         data() {    
  49.             return {    
  50.                 el: {    
  51.     
  52.                 },    
  53.                 basicdata: {    
  54.                     start: {},    
  55.                     end: {}    
  56.                 },    
  57.                 temporaryData: {    
  58.                     isStackClick: true,    
  59.                     offsetY: '',    
  60.                     poswidth: 0,    
  61.                     posheight: 0,    
  62.                     lastPosWidth: '',    
  63.                     lastPosHeight: '',    
  64.                     lastZindex: '',    
  65.                     rotate: 0,    
  66.                     lastRotate: 0,    
  67.                     visible: this.stackInit.visible || 3, // 展示几个  
  68.                     tracking: false,    
  69.                     animation: false,    
  70.                     currentPage: this.stackInit.currentPage || 0, // 当前第几个    
  71.                     opacity: 1,    
  72.                     lastOpacity: 0,    
  73.                     swipe: false,    
  74.                     zIndex: 10    
  75.                 }    
  76.     
  77.             }    
  78.         },    
  79.         computed: {    
  80.             // 划出面积比例    
  81.             offsetRatio() {    
  82.                 let width = this.el.width    
  83.                 let height = this.el.height    
  84.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)    
  85.                 let offsetHeight = height - Math.abs(this.temporaryData.posheight)    
  86.                 let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0    
  87.                 return ratio > 1 ? 1 : ratio    
  88.             },    
  89.             // 划出宽度比例    
  90.             offsetWidthRatio() {    
  91.                 let width = this.el.width    
  92.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)    
  93.                 let ratio = 1 - offsetWidth / width || 0    
  94.                 return ratio    
  95.             }    
  96.         },    
  97.         created() {    
  98.             this.$nextTick(async e => {    
  99.                 this.el = await getElSize('flipcard', this)    
  100.             })    
  101.         },    
  102.         methods: {    
  103.             touchstart(e) {    
  104.                 if (this.temporaryData.tracking) {    
  105.                     return    
  106.                 }    
  107.                 // 是否为touch    
  108.                 const point = getTouchPoint(e)     
  109.                 // console.log(point)    
  110.                 if (e.type === 'touchstart') {    
  111.                     if (e.touches.length > 1) {    
  112.                         this.temporaryData.tracking = false    
  113.                         return    
  114.                     } else {    
  115.                         // 记录起始位置    
  116.                         this.basicdata.start.t = new Date().getTime()    
  117.                         this.basicdata.start.x = point.x     
  118.                         this.basicdata.start.y = point.y    
  119.                         this.basicdata.end.x = point.x     
  120.                         this.basicdata.end.y = point.y    
  121.                         // offsetY在touch事件中没有,只能自己计算    
  122.                         this.temporaryData.offsetY = point.y - this.el.top    
  123.                     }    
  124.                 }    
  125.                 this.temporaryData.isStackClick = true    
  126.                 this.temporaryData.tracking = true    
  127.                 this.temporaryData.animation = false    
  128.             },    
  129.             touchmove(e) {    
  130.                 this.temporaryData.isStackClick = false    
  131.                 // 记录滑动位置    
  132.                 if (this.temporaryData.tracking && !this.temporaryData.animation) {    
  133.                     const point = getTouchPoint(e)    
  134.                     e.preventDefault()    
  135.                     this.basicdata.end.x = point.x    
  136.                     this.basicdata.end.y = point.y    
  137.                     // 计算滑动值    
  138.                     thisthis.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x    
  139.                     thisthis.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y    
  140.                     let rotateDirection = this.rotateDirection()    
  141.                     let angleRatio = this.angleRatio()    
  142.                     this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio    
  143.                 }    
  144.             },    
  145.             touchend(e, index) {    
  146.                 if (this.temporaryData.isStackClick) {    
  147.                     this.$emit('click', index)    
  148.                     this.temporaryData.isStackClick = false    
  149.                 }    
  150.                 this.temporaryData.isStackClick = true    
  151.                 this.temporaryData.tracking = false    
  152.                 this.temporaryData.animation = true    
  153.                 // 滑动结束,触发判断    
  154.                 // 判断划出面积是否大于0.4    
  155.                 if (this.offsetRatio >= 0.4) {    
  156.                     // 计算划出后最终位置    
  157.                     let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)    
  158.                     thisthis.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 :    
  159.                         this.temporaryData.poswidth - 200    
  160.                     thisthis.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData    
  161.                         .poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)    
  162.                     this.temporaryData.opacity = 0    
  163.                     this.temporaryData.swipe = true    
  164.                     this.nextTick()    
  165.                     // 不满足条件则滑入    
  166.                 } else {    
  167.                     this.temporaryData.poswidth = 0    
  168.                     this.temporaryData.posheight = 0    
  169.                     this.temporaryData.swipe = false    
  170.                     this.temporaryData.rotate = 0    
  171.                 }    
  172.             },    
  173.             nextTick() {    
  174.                 // 记录最终滑动距离    
  175.                 thisthis.temporaryData.lastPosWidth = this.temporaryData.poswidth    
  176.                 thisthis.temporaryData.lastPosHeight = this.temporaryData.posheight    
  177.                 thisthis.temporaryData.lastRotate = this.temporaryData.rotate    
  178.                 this.temporaryData.lastZindex = 20    
  179.                 // 循环currentPage    
  180.                 thisthis.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this    
  181.                     .temporaryData.currentPage + 1    
  182.                 // currentPage切换,整体dom进行变化,把第一层滑动置最低    
  183.                 this.$nextTick(() => {    
  184.                     this.temporaryData.poswidth = 0    
  185.                     this.temporaryData.posheight = 0    
  186.                     this.temporaryData.opacity = 1    
  187.                     this.temporaryData.rotate = 0    
  188.                 })    
  189.             },    
  190.             onTransitionEnd(index) {    
  191.                 let lastPage = this.temporaryData.currentPage === 0 ? this.pages.length - 1 : this.temporaryData    
  192.                     .currentPage - 1    
  193.                 // dom发生变化正在执行的动画滑动序列已经变为上一层    
  194.                 if (this.temporaryData.swipe && index === lastPage) {    
  195.                     this.temporaryData.animation = true    
  196.                     this.temporaryData.lastPosWidth = 0    
  197.                     this.temporaryData.lastPosHeight = 0    
  198.                     this.temporaryData.lastOpacity = 0    
  199.                     this.temporaryData.lastRotate = 0    
  200.                     this.temporaryData.swipe = false    
  201.                     this.temporaryData.lastZindex = -1    
  202.                 }    
  203.             },    
  204.             prev() {    
  205.                 this.temporaryData.tracking = false    
  206.                 this.temporaryData.animation = true    
  207.                 // 计算划出后最终位置    
  208.                 let width = this.el.width    
  209.                 this.temporaryData.poswidth = -width    
  210.                 this.temporaryData.posheight = 0    
  211.                 this.temporaryData.opacity = 0    
  212.                 this.temporaryData.rotate = '-3'    
  213.                 this.temporaryData.swipe = true    
  214.                 this.nextTick()    
  215.             },    
  216.             next() {    
  217.                 this.temporaryData.tracking = false    
  218.                 this.temporaryData.animation = true    
  219.                 // 计算划出后最终位置    
  220.                 let width = this.el.width    
  221.                 this.temporaryData.poswidth = width    
  222.                 this.temporaryData.posheight = 0    
  223.                 this.temporaryData.opacity = 0    
  224.                 this.temporaryData.rotate = '3'    
  225.                 this.temporaryData.swipe = true    
  226.                 this.nextTick()    
  227.             },    
  228.             rotateDirection() {    
  229.                 if (this.temporaryData.poswidth <= 0) {    
  230.                     return -1    
  231.                 } else {    
  232.                     return 1    
  233.                 }    
  234.             },    
  235.             angleRatio() {    
  236.                 let height = this.el.height    
  237.                 let offsetY = this.temporaryData.offsetY    
  238.                 let ratio = -1 * (2 * offsetY / height - 1)    
  239.                 return ratio || 0    
  240.             },    
  241.             inStack(index, currentPage) {    
  242.                 let stack = []    
  243.                 let visible = this.temporaryData.visible    
  244.                 let length = this.pages.length    
  245.                 for (let i = 0; i < visible; i++) {    
  246.                     if (currentPage + i < length) {    
  247.                         stack.push(currentPage + i)    
  248.                     } else {    
  249.                         stack.push(currentPage + i - length)    
  250.                     }    
  251.                 }    
  252.                 return stack.indexOf(index) >= 0    
  253.             },    
  254.             // 非首页样式切换    
  255.             transform(index) {    
  256.                 let currentPage = this.temporaryData.currentPage    
  257.                 let length = this.pages.length    
  258.                 let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1    
  259.                 let style = {}    
  260.                 let visible = this.temporaryData.visible    
  261.                 if (index === this.temporaryData.currentPage) {    
  262.                     return    
  263.                 }    
  264.                 if (this.inStack(index, currentPage)) {    
  265.                     let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length    
  266.                     style['opacity'] = '1'    
  267.                     style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')'    
  268.                     style['zIndex'] = visible - perIndex    
  269.                     if (!this.temporaryData.tracking) {    
  270.                         style['transitionTimingFunction'] = 'ease'    
  271.                         style['transitionDuration'] = 300 + 'ms'    
  272.                     }    
  273.                 } else if (index === lastPage) {    
  274.                     style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData    
  275.                         .lastPosHeight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.lastRotate + 'deg)'    
  276.                     style['opacity'] = this.temporaryData.lastOpacity    
  277.                     style['zIndex'] = this.temporaryData.lastZindex    
  278.                     style['transitionTimingFunction'] = 'ease'    
  279.                     style['transitionDuration'] = 300 + 'ms'    
  280.                 } else {    
  281.                     style['zIndex'] = '-1'    
  282.                     style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'    
  283.                 }    
  284.                 return style    
  285.             },    
  286.             // 首页样式切换    
  287.             transformIndex(index) {    
  288.                 if (index === this.temporaryData.currentPage) {    
  289.                     let style = {}    
  290.                     style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData    
  291.                         .posheight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.rotate + 'deg)'    
  292.                     style['opacity'] = this.temporaryData.opacity    
  293.                     style['zIndex'] = 10    
  294.                     if (this.temporaryData.animation) {    
  295.                         style['transitionTimingFunction'] = 'ease'    
  296.                         style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'    
  297.                     }    
  298.                     return style    
  299.                 }    
  300.             },    
  301.         }    
  302.     }    
  303. </script>    
  304.     
  305. <style lang="less" scoped>    
  306.     .flipcard {    
  307.         width: 100%;    
  308.         height: 100%;    
  309.         position: relative;    
  310.         perspective: 1000px;    
  311.         perspective-origin: 50% 150%;    
  312.         -webkit-perspective: 1000px;    
  313.         -webkit-perspective-origin: 50% 150%;    
  314.         margin: 0;    
  315.         padding: 0;    
  316.     
  317.         &-item {    
  318.             background: #fff;    
  319.             height: 100%;    
  320.             width: 100%;    
  321.             border-radius: 30rpx;    
  322.             overflow: hidden;    
  323.             position: absolute;    
  324.             opacity: 0;    
  325.             display: -webkit-flex;    
  326.             display: flex;    
  327.             -webkit-flex-direction: column;    
  328.             flex-direction: column;    
  329.             -webkit-touch-callout: none;    
  330.             -webkit-user-select: none;    
  331.             -khtml-user-select: none;    
  332.             -moz-user-select: none;    
  333.             -ms-user-select: none;    
  334.             user-select: none;    
  335.             pointer-events: auto;    
  336.             padding: 30rpx;    
  337.             /* #ifndef APP-NVUE */    
  338.             box-sizing: border-box;    
  339.             /* #endif */    
  340.             box-shadow: 0px 2rpx 30rpx 0px rgba(94, 114, 153, 0.3);    
  341.     
  342.     
  343.             &-stack-info {    
  344.     
  345.                 .t3 {    
  346.                     padding: 0 0 25rpx 0;    
  347.     
  348.                     .t3-text {    
  349.                         font-size: 26rpx;    
  350.                         background-color: #ECECEC;    
  351.                         padding: 8rpx 18rpx;    
  352.                         color: #999999;    
  353.                         border-radius: 10rpx;    
  354.                     }    
  355.                 }    
  356.     
  357.                 .problem {    
  358.                     height: 180rpx;    
  359.                     overflow: hidden;    
  360.                     text-overflow: ellipsis;    
  361.     
  362.                     .problem-text {    
  363.                         font-size: 32rpx;    
  364.                         color: #333333;    
  365.                         line-height: 46rpx;    
  366.                     }    
  367.                 }    
  368.     
  369.                 .desc {    
  370.                     margin-top: 36rpx;    
  371.     
  372.                     .desc-text {    
  373.                         font-size: 26rpx;    
  374.                         color: #999999;    
  375.                     }    
  376.                 }    
  377.     
  378.                 .bottom-box {    
  379.                     bottom: 30rpx;    
  380.                     left: 30rpx;    
  381.     
  382.                     .avatar {    
  383.                         width: 80rpx;    
  384.                         height: 80rpx;    
  385.                         border-radius: 50%;    
  386.                         margin-right: 20rpx;    
  387.                     }    
  388.     
  389.                     .name {    
  390.                         font-size: 28rpx;    
  391.                         color: #AAAAAA;    
  392.                     }    
  393.                 }    
  394.             }    
  395.         }    
  396.     }    
  397. </style>  

 

 

动画效果:仿知乎只想要左右划出,上下不动

7.gif

覆盖其中一段即可:

JavaScript Code复制内容到剪贴板
  1. // 首页样式切换    
  2. transformIndex(index) {    
  3.     if (index === this.temporaryData.currentPage) {    
  4.         let style = {}    
  5.         style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',0px' + ',0px) ' + 'rotate(' + -this.temporaryData.rotate + 'deg)'    
  6.         style['opacity'] = this.temporaryData.opacity    
  7.         style['zIndex'] = 10    
  8.         if (this.temporaryData.animation) {    
  9.             style['transitionTimingFunction'] = 'ease'    
  10.             style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'    
  11.         }    
  12.         return style    
  13.     }    
  14. },  

 

 

 


 

2021.08.25 移动和点击事件相冲突时,需要将handleStackClicked点击事件暴露:

组件直接复制

XML/HTML Code复制内容到剪贴板
  1. <template>  
  2.     <div class="flipcard" id="flipcard" ref="flipcard">  
  3.         <div class="flipcard-item" v-for="(item, index) in pages" :key="index"  
  4.             :style="[transformIndex(index),transform(index)]" @touchstart="touchstart" @touchmove="touchmove"  
  5.             @touchend="touchend" @touchcancel="touchend" @transitionend="onTransitionEnd(index,'transitionend')">  
  6.             <div class="flipcard-item-stack-info">  
  7.                 <div class="t3"><text class="t3-text">{{item.tag}}</text></div>  
  8.                 <div class="problem">  
  9.                     <text class="problem-text">{{item.problem}}</text>  
  10.                 </div>  
  11.                 <div class="desc n-flex-row n-wrap-nowrap n-align-center">  
  12.                     <text class="desc-text">{{item.attention_num}}人关注</text>  
  13.                     <text class="desc-text">·</text>  
  14.                     <text class="desc-text">{{item.comment_num}}人回答</text>  
  15.                 </div>  
  16.                 <div class="n-flex-row n-wrap-nowrap n-align-center n-position-absolute bottom-box">  
  17.                     <image class="avatar" :src="item.author.avatar" mode="aspectFill"></image>  
  18.                     <text class="name">{{item.author.name}}</text>  
  19.                 </div>  
  20.             </div>  
  21.         </div>  
  22.     </div>  
  23. </template>  
  24.   
  25. <script>  
  26.     /**  
  27.      * @Desc     Vue仿探探/知乎|Tinder卡片滑动FlipCard  
  28.      * @Time     by 2021-08-24  
  29.      * @About    Q:398927951  
  30.      */  
  31.     import {  
  32.         getTouchPoint,  
  33.         getElSize  
  34.     } from '@/nPro/utils/element.js'  
  35.   
  36.     export default {  
  37.         props: {  
  38.             pages: {  
  39.                 type: Array,  
  40.                 default: []  
  41.             },  
  42.             stackInit: {  
  43.                 type: Object,  
  44.                 default: {}  
  45.             },  
  46.         },  
  47.         data() {  
  48.             return {  
  49.                 el: {  
  50.   
  51.                 },  
  52.                 basicdata: {  
  53.                     start: {},  
  54.                     end: {}  
  55.                 },  
  56.                 temporaryData: {  
  57.                     isStackClick: true, // 多了一个参数  
  58.                     offsetY: '',  
  59.                     poswidth: 0,  
  60.                     posheight: 0,  
  61.                     lastPosWidth: '',  
  62.                     lastPosHeight: '',  
  63.                     lastZindex: '',  
  64.                     rotate: 0,  
  65.                     lastRotate: 0,  
  66.                     visible: this.stackInit.visible || 3, // 展示几个  
  67.                     tracking: false,  
  68.                     animation: false,  
  69.                     currentPage: this.stackInit.currentPage || 0, // 当前第几个  
  70.                     opacity: 1,  
  71.                     lastOpacity: 0,  
  72.                     swipe: false,  
  73.                     zIndex: 10  
  74.                 }  
  75.             }  
  76.         },  
  77.         computed: {  
  78.             // 划出面积比例  
  79.             offsetRatio(e) {  
  80.                 let width = this.el.width  
  81.                 let height = this.el.height  
  82.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)  
  83.                 let offsetHeight = height - Math.abs(this.temporaryData.posheight)  
  84.                 let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0  
  85.                 return ratio > 1 ? 1 : ratio  
  86.             },  
  87.             // 划出宽度比例  
  88.             offsetWidthRatio() {  
  89.                 let width = this.el.offsetWidth  
  90.                 let offsetWidth = width - Math.abs(this.temporaryData.poswidth)  
  91.                 let ratio = 1 - offsetWidth / width || 0  
  92.                 return ratio  
  93.             }  
  94.         },  
  95.         created() {  
  96.             this.$nextTick(async e => {  
  97.                 this.el = await getElSize('flipcard', this)  
  98.             })  
  99.         },  
  100.         methods: {  
  101.             touchstart(e) {  
  102.                 if (this.temporaryData.tracking) {  
  103.                     return  
  104.                 }  
  105.                 // 是否为touch  
  106.                 if (e.type === 'touchstart') {  
  107.                     if (e.touches.length > 1) {  
  108.                         this.temporaryData.tracking = false  
  109.                         return  
  110.                     } else {  
  111.                         // 记录起始位置  
  112.                         const point = getTouchPoint(e)  
  113.   
  114.                         // console.log("记录起始位置",point);  
  115.                         this.basicdata.start.t = new Date().getTime()  
  116.                         this.basicdata.start.x = point.x  
  117.                         this.basicdata.start.y = point.y  
  118.                         this.basicdata.end.x = point.x  
  119.                         this.basicdata.end.y = point.y  
  120.                         // offsetY在touch事件中没有,只能自己计算  
  121.                         this.temporaryData.offsetY = point.y - this.el.top  
  122.                         // console.log(this.temporaryData.offsetY)  
  123.                     }  
  124.                     // pc操作  
  125.                 } else {  
  126.                     console.log("pc记录起始位置");  
  127.                     uni.showToast({  
  128.                         title: '无效操作',  
  129.                         icon: 'none'  
  130.                     });  
  131.                     // this.basicdata.start.t = new Date().getTime()  
  132.                     // this.basicdata.start.x = e.clientX  
  133.                     // this.basicdata.start.y = e.clientY  
  134.                     // this.basicdata.end.x = e.clientX  
  135.                     // this.basicdata.end.y = e.clientY  
  136.                     // this.temporaryData.offsetY = e.offsetY  
  137.                 }  
  138.                 this.temporaryData.isStackClick = true // 多了一个参数  
  139.                 this.temporaryData.tracking = true  
  140.                 this.temporaryData.animation = false  
  141.             },  
  142.             touchmove(e) {  
  143.                 this.temporaryData.isStackClick = false // 多了一个参数  
  144.                 // 记录滑动位置  
  145.                 if (this.temporaryData.tracking && !this.temporaryData.animation) {  
  146.                     const point = getTouchPoint(e)  
  147.                     if (e.type === 'touchmove') {  
  148.                         e.preventDefault()  
  149.                         this.basicdata.end.x = point.x  
  150.                         this.basicdata.end.y = point.y  
  151.                     } else {  
  152.                         e.preventDefault()  
  153.                         this.basicdata.end.x = point.x  
  154.                         this.basicdata.end.y = point.y  
  155.                     }  
  156.                     // 计算滑动值  
  157.                     thisthis.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x  
  158.                     thisthis.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y  
  159.                     let rotateDirection = this.rotateDirection()  
  160.                     let angleRatio = this.angleRatio()  
  161.                     this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio  
  162.                 }  
  163.             },  
  164.             touchend(e, index) {  
  165.                 console.log(this.temporaryData.isStackClick)  
  166.                 if(this.temporaryData.isStackClick) {  
  167.                     this.$emit('click', index) // 多了一个参数,触发上层传递事件  
  168.                     this.temporaryData.isStackClick = false  
  169.                 }  
  170.                 this.temporaryData.isStackClick = true // 多了一个参数  
  171.                 this.temporaryData.tracking = false  
  172.                 this.temporaryData.animation = true  
  173.                 // 滑动结束,触发判断  
  174.                 // 判断划出面积是否大于0.4  
  175.                 if (this.offsetRatio >= 0.4) {  
  176.                     // 计算划出后最终位置  
  177.                     let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)  
  178.                     thisthis.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 :  
  179.                         this.temporaryData.poswidth - 200  
  180.                     thisthis.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData  
  181.                         .poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)  
  182.                     this.temporaryData.opacity = 0  
  183.                     this.temporaryData.swipe = true  
  184.                     this.nextTick()  
  185.                     // 不满足条件则滑入  
  186.                 } else {  
  187.                     this.temporaryData.poswidth = 0  
  188.                     this.temporaryData.posheight = 0  
  189.                     this.temporaryData.swipe = false  
  190.                     this.temporaryData.rotate = 0  
  191.                 }  
  192.             },  
  193.             nextTick() {  
  194.                 // 记录最终滑动距离  
  195.                 thisthis.temporaryData.lastPosWidth = this.temporaryData.poswidth  
  196.                 thisthis.temporaryData.lastPosHeight = this.temporaryData.posheight  
  197.                 thisthis.temporaryData.lastRotate = this.temporaryData.rotate  
  198.                 this.temporaryData.lastZindex = 20  
  199.                 // 循环currentPage  
  200.                 thisthis.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this  
  201.                     .temporaryData.currentPage + 1  
  202.                 // currentPage切换,整体dom进行变化,把第一层滑动置最低  
  203.                 this.$nextTick(() => {  
  204.                     this.temporaryData.poswidth = 0  
  205.                     this.temporaryData.posheight = 0  
  206.                     this.temporaryData.opacity = 1  
  207.                     this.temporaryData.rotate = 0  
  208.                 })  
  209.             },  
  210.             onTransitionEnd(index, log) {  
  211.                 let lastPage = this.temporaryData.currentPage === 0 ? this.pages.length - 1 : this.temporaryData  
  212.                     .currentPage - 1  
  213.                 // dom发生变化正在执行的动画滑动序列已经变为上一层  
  214.                 if (this.temporaryData.swipe && index === lastPage) {  
  215.                     this.temporaryData.animation = true  
  216.                     this.temporaryData.lastPosWidth = 0  
  217.                     this.temporaryData.lastPosHeight = 0  
  218.                     this.temporaryData.lastOpacity = 0  
  219.                     this.temporaryData.lastRotate = 0  
  220.                     this.temporaryData.swipe = false  
  221.                     this.temporaryData.lastZindex = -1  
  222.                 }  
  223.             },  
  224.             prev() {  
  225.                 this.temporaryData.tracking = false  
  226.                 this.temporaryData.animation = true  
  227.                 // 计算划出后最终位置  
  228.                 let width = this.el.width  
  229.                 this.temporaryData.poswidth = -width  
  230.                 this.temporaryData.posheight = 0  
  231.                 this.temporaryData.opacity = 0  
  232.                 this.temporaryData.rotate = '-3'  
  233.                 this.temporaryData.swipe = true  
  234.                 this.nextTick()  
  235.             },  
  236.             next() {  
  237.                 this.temporaryData.tracking = false  
  238.                 this.temporaryData.animation = true  
  239.                 // 计算划出后最终位置  
  240.                 let width = this.el.width  
  241.                 this.temporaryData.poswidth = width  
  242.                 this.temporaryData.posheight = 0  
  243.                 this.temporaryData.opacity = 0  
  244.                 this.temporaryData.rotate = '3'  
  245.                 this.temporaryData.swipe = true  
  246.                 this.nextTick()  
  247.             },  
  248.             rotateDirection() {  
  249.                 if (this.temporaryData.poswidth <= 0) {  
  250.                     return -1  
  251.                 } else {  
  252.                     return 1  
  253.                 }  
  254.             },  
  255.             angleRatio() {  
  256.                 let height = this.el.height  
  257.                 let offsetY = this.temporaryData.offsetY  
  258.                 let ratio = -1 * (2 * offsetY / height - 1)  
  259.                 return ratio || 0  
  260.             },  
  261.             inStack(index, currentPage) {  
  262.                 let stack = []  
  263.                 let visible = this.temporaryData.visible  
  264.                 let length = this.pages.length  
  265.                 for (let i = 0; i < visible; i++) {  
  266.                     if (currentPage + i < length) {  
  267.                         stack.push(currentPage + i)  
  268.                     } else {  
  269.                         stack.push(currentPage + i - length)  
  270.                     }  
  271.                 }  
  272.                 return stack.indexOf(index) >= 0  
  273.             },  
  274.             // 非首页样式切换  
  275.             transform(index) {  
  276.                 let currentPage = this.temporaryData.currentPage  
  277.                 let length = this.pages.length  
  278.                 let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1  
  279.                 let style = {}  
  280.                 let visible = this.temporaryData.visible  
  281.                 if (index === this.temporaryData.currentPage) {  
  282.                     return  
  283.                 }  
  284.                 if (this.inStack(index, currentPage)) {  
  285.                     let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length  
  286.                     style['opacity'] = '1'  
  287.                     style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')'  
  288.                     style['zIndex'] = visible - perIndex  
  289.                       
  290.                     if (!this.temporaryData.tracking) {    
  291.                         style['transitionTimingFunction'] = 'ease'    
  292.                         style['transitionDuration'] = 300 + 'ms'    
  293.                     }  
  294.                 } else if (index === lastPage) {  
  295.                     style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData  
  296.                         .lastPosHeight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.lastRotate + 'deg)'  
  297.                     style['opacity'] = this.temporaryData.lastOpacity  
  298.                     style['zIndex'] = this.temporaryData.lastZindex  
  299.                     style['transitionTimingFunction'] = 'ease'    
  300.                     style['transitionDuration'] = 300 + 'ms'  
  301.                 } else {  
  302.                     style['zIndex'] = '-1'  
  303.                     style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'  
  304.                 }  
  305.                 return style  
  306.             },  
  307.             // 首页样式切换  
  308.             transformIndex(index) {  
  309.                 if (index === this.temporaryData.currentPage) {  
  310.                     let style = {}  
  311.                     style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData  
  312.                         .posheight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.rotate + 'deg)'  
  313.                     style['opacity'] = this.temporaryData.opacity  
  314.                     style['zIndex'] = 10  
  315.                     if (this.temporaryData.animation) {  
  316.                         style['transitionTimingFunction'] = 'ease'    
  317.                         style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'    
  318.                     }  
  319.                     return style  
  320.                 }  
  321.             },  
  322.         }  
  323.     }  
  324. </script>  
  325.   
  326. <style lang="less" scoped>  
  327.     .flipcard {  
  328.         width: 100%;  
  329.         height: 100%;  
  330.         position: relative;  
  331.         perspective: 1000px;  
  332.         perspective-origin: 50% 150%;  
  333.         -webkit-perspective: 1000px;  
  334.         -webkit-perspective-origin: 50% 150%;  
  335.         margin: 0;  
  336.         padding: 0;  
  337.   
  338.         &-item {  
  339.             background: #fff;  
  340.             height: 100%;  
  341.             width: 100%;  
  342.             border-radius: 30rpx;  
  343.             overflow: hidden;  
  344.             position: absolute;  
  345.             opacity: 0;  
  346.             display: -webkit-flex;  
  347.             display: flex;  
  348.             -webkit-flex-direction: column;  
  349.             flex-direction: column;  
  350.             -webkit-touch-callout: none;  
  351.             -webkit-user-select: none;  
  352.             -khtml-user-select: none;  
  353.             -moz-user-select: none;  
  354.             -ms-user-select: none;  
  355.             user-select: none;  
  356.             pointer-events: auto;  
  357.             padding: 30rpx;  
  358.             /* #ifndef APP-NVUE */  
  359.             box-sizing: border-box;  
  360.             /* #endif */  
  361.             box-shadow: 0px 2rpx 30rpx 0px rgba(94, 114, 153, 0.3);  
  362.   
  363.   
  364.             &-stack-info {  
  365.   
  366.                 .t3 {  
  367.                     padding: 0 0 25rpx 0;  
  368.   
  369.                     .t3-text {  
  370.                         font-size: 26rpx;  
  371.                         background-color: #ECECEC;  
  372.                         padding: 8rpx 18rpx;  
  373.                         color: #999999;  
  374.                         border-radius: 10rpx;  
  375.                     }  
  376.                 }  
  377.   
  378.                 .problem {  
  379.                     height: 180rpx;  
  380.                     overflow: hidden;  
  381.                     text-overflow: ellipsis;  
  382.   
  383.                     .problem-text {  
  384.                         font-size: 32rpx;  
  385.                         color: #333333;  
  386.                         line-height: 46rpx;  
  387.                     }  
  388.                 }  
  389.   
  390.                 .desc {  
  391.                     margin-top: 36rpx;  
  392.   
  393.                     .desc-text {  
  394.                         font-size: 26rpx;  
  395.                         color: #999999;  
  396.                     }  
  397.                 }  
  398.   
  399.                 .bottom-box {  
  400.                     bottom: 30rpx;  
  401.                     left: 30rpx;  
  402.   
  403.                     .avatar {  
  404.                         width: 80rpx;  
  405.                         height: 80rpx;  
  406.                         border-radius: 50%;  
  407.                         margin-right: 20rpx;  
  408.                     }  
  409.   
  410.                     .name {  
  411.                         font-size: 28rpx;  
  412.                         color: #AAAAAA;  
  413.                     }  
  414.                 }  
  415.             }  
  416.         }  
  417.     }  
  418. </style>  

 

 

 

 

本文来自于:https://www.v2ex.com/t/714576#reply0

Powered by yoyo苏ICP备15045725号