基于Vue.js|Nuxt.js实现探探卡片滑动切换效果

陌陌|探探社交App中拖拽滑动翻牌子效果让人印象深刻,最近在开发Nuxt项目,需要实现类似这个效果,于是经过多次调试,最终实现了,现整理作些简单的分享,感兴趣的可以看下哈~~

效果预览

怎么样,是不是觉得还行... 下面就简单的讲解下具体的实现方法。

页面布局

页面整体分为 顶部导航、翻牌子区域、底部Tabbar 三个部分。

布局模板

<!-- //翻一翻模板 -->
<template><div><!-- >>顶部 --><header-bar :back="false" bgcolor="linear-gradient(to right, #00e0a1, #00a1ff)" fixed><div slot="title"><i class="iconfont icon-like c-red"></i> <em class="ff-gg">遇见TA</em></div><div slot="right" class="ml-30" @click="showFilter = true"><i class="iconfont icon-filter"></i></div></header-bar><!-- >>主页面 --><div class="nuxt__scrollview scrolling flex1" ref="scrollview" style="background: linear-gradient(to right, #00e0a1, #00a1ff);"><div class="nt__flipcard"><div class="nt__stack-wrapper"><flipcard ref="stack" :pages="stackList" @click="handleStackClicked"></flipcard></div><div class="nt__stack-control flexbox"><button class="btn-ctrl prev" @click="handleStackPrev"><i class="iconfont icon-unlike "></i></button><button class="btn-ctrl next" @click="handleStackNext"><i class="iconfont icon-like "></i></button></div></div></div><!-- >>底部tabbar --><tab-bar bgcolor="linear-gradient(to right, #00e0a1, #00a1ff)" color="#fff" /></div>
</template>

侧边栏弹框

点击右上角筛选,在侧边会出现弹窗。其中范围滑块、switch开关、Rate评分等组件则是使用Vant组件库。

<template><!-- ... --><!-- @@侧边栏弹框模板 --><v-popup v-model="showFilter" position="left" xclose xposition="left" title="高级筛选与设置"><div class="flipcard-filter"><div class="item nuxt-cell"><label class="lbl">范围</label><div class="flex1"><van-slider v-model="distanceRange" bar-height="2px" button-size="12px" active-color="#00e0a1" min="1" @input="handleDistanceRange" /></div><em class="val">{{distanceVal}}</em></div><div class="item nuxt-cell"><label class="lbl flex1">自动增加范围</label><em class="val"><van-switch v-model="autoExpand" size="20px" active-color="#00e0a1" /></em></div><div class="item nuxt-cell"><label class="lbl flex1">性别</label><em class="val">女生</em></div><div class="item nuxt-cell"><label class="lbl">好评度</label><div class="flex1"><van-rate v-model="starVal" color="#00e0a1" icon="like" void-icon="like-o" @change="handleStar" /></div><em class="val">{{starVal}}星</em></div><div class="item nuxt-cell"><label class="lbl flex1">优先在线用户</label><em class="val"><van-switch v-model="firstOnline" size="20px" active-color="#00e0a1" /></em></div><div class="item nuxt-cell"><label class="lbl flex1">优先新用户</label><em class="val"><van-switch v-model="firstNewUser" size="20px" active-color="#00e0a1" /></em></div><div class="item nuxt-cell mt-20"><div class="mt-30 nuxt__btn nuxt__btn-primary--gradient" style="height:38px;"><i class="iconfont icon-filter"></i> 更新</div></div></div></v-popup>
</template><script>export default {// 用于配置应用默认的 meta 标签head() {return {title: `${this.title} - 翻一翻`,meta: [{name:'keywords',hid: 'keywords',content:`${this.title} | 翻一翻 | 翻动卡片`},{name:'description',hid:'description',content:`${this.title} | 仿探探卡片翻动`}]}},middleware: 'auth',data () {return {title: 'Nuxt',showFilter: false,distanceRange: 1,distanceVal: '<1km',autoExpand: true,starVal: 5,firstOnline: false,firstNewUser: true,// ...}},methods: {/* @@左侧筛选函数 */// 范围选择handleDistanceRange(val) {if(val == 1) {this.distanceVal = '<1km';} else if (val == 100) {this.distanceVal = "100km+"}else {this.distanceVal = val+'km';}},// 好评度handleStar(val) {this.starVal = val;},// ...},}
</script>

仿探探卡片滑动

卡片区单独封装了一个组件flipcard,只需传入pages数据就可以。

<flipcard ref="stack" :pages="stackList"></flipcard>

在四周拖拽卡片会出现不同的斜切视角。

pages支持的数据格式

module.exports = [{avatar: '/assets/img/avatar02.jpg',name: '放荡不羁爱自由',sex: 'female',age: 23,starsign: '天秤座',distance: '艺术/健身',photos: [...],sign: '交个朋友,非诚勿扰'},...
]

flipcard.vue模板

<template><ul class="stack"><li class="stack-item" v-for="(item, index) in pages" :key="index" :style="[transformIndex(index),transform(index)]"@touchmove.stop.capture="touchmove"@touchstart.stop.capture="touchstart"@touchend.stop.capture="touchend($event, index)"@touchcancel.stop.capture="touchend($event, index)"@mousedown.stop.capture.prevent="touchstart"@mouseup.stop.capture.prevent="touchend($event, index)"@mousemove.stop.capture.prevent="touchmove"@mouseout.stop.capture.prevent="touchend($event, index)"@webkit-transition-end="onTransitionEnd(index)"@transitionend="onTransitionEnd(index)"><img :src="item.avatar" /><div class="stack-info"><h2 class="name">{{item.name}}</h2><p class="tags"><span class="sex" :class="item.sex"><i class="iconfont" :class="'icon-'+item.sex"></i> {{item.age}}</span><span class="xz">{{item.starsign}}</span></p><p class="distance">{{item.distance}}</p></div></li></ul>
</template>
/*** @Desc     Vue仿探探|Tinder卡片滑动FlipCard* @Time     andy by 2020-10-06* @About    Q:282310962  wx:xy190310*/
<script>export default {props: {pages: {type: Array,default: {}}},data () {return {basicdata: {start: {},end: {}},temporaryData: {isStackClick: true,offsetY: '',poswidth: 0,posheight: 0,lastPosWidth: '',lastPosHeight: '',lastZindex: '',rotate: 0,lastRotate: 0,visible: 3,tracking: false,animation: false,currentPage: 0,opacity: 1,lastOpacity: 0,swipe: false,zIndex: 10}}},computed: {// 划出面积比例offsetRatio () {let width = this.$el.offsetWidthlet height = this.$el.offsetHeightlet offsetWidth = width - Math.abs(this.temporaryData.poswidth)let offsetHeight = height - Math.abs(this.temporaryData.posheight)let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0return ratio > 1 ? 1 : ratio},// 划出宽度比例offsetWidthRatio () {let width = this.$el.offsetWidthlet offsetWidth = width - Math.abs(this.temporaryData.poswidth)let ratio = 1 - offsetWidth / width || 0return ratio}},methods: {touchstart (e) {if (this.temporaryData.tracking) {return}// 是否为touchif (e.type === 'touchstart') {if (e.touches.length > 1) {this.temporaryData.tracking = falsereturn} else {// 记录起始位置this.basicdata.start.t = new Date().getTime()this.basicdata.start.x = e.targetTouches[0].clientXthis.basicdata.start.y = e.targetTouches[0].clientYthis.basicdata.end.x = e.targetTouches[0].clientXthis.basicdata.end.y = e.targetTouches[0].clientY// offsetY在touch事件中没有,只能自己计算this.temporaryData.offsetY = e.targetTouches[0].pageY - this.$el.offsetParent.offsetTop}// pc操作} else {this.basicdata.start.t = new Date().getTime()this.basicdata.start.x = e.clientXthis.basicdata.start.y = e.clientYthis.basicdata.end.x = e.clientXthis.basicdata.end.y = e.clientYthis.temporaryData.offsetY = e.offsetY}this.temporaryData.isStackClick = truethis.temporaryData.tracking = truethis.temporaryData.animation = false},touchmove (e) {this.temporaryData.isStackClick = false// 记录滑动位置if (this.temporaryData.tracking && !this.temporaryData.animation) {if (e.type === 'touchmove') {e.preventDefault()this.basicdata.end.x = e.targetTouches[0].clientXthis.basicdata.end.y = e.targetTouches[0].clientY} else {e.preventDefault()this.basicdata.end.x = e.clientXthis.basicdata.end.y = e.clientY}// 计算滑动值this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.xthis.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.ylet rotateDirection = this.rotateDirection()let angleRatio = this.angleRatio()this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio}},touchend (e, index) {if(this.temporaryData.isStackClick) {this.$emit('click', index)this.temporaryData.isStackClick = false}this.temporaryData.isStackClick = truethis.temporaryData.tracking = falsethis.temporaryData.animation = true// 滑动结束,触发判断// 判断划出面积是否大于0.4if (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 - 200this.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData.poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)this.temporaryData.opacity = 0this.temporaryData.swipe = truethis.nextTick()// 不满足条件则滑入} else {this.temporaryData.poswidth = 0this.temporaryData.posheight = 0this.temporaryData.swipe = falsethis.temporaryData.rotate = 0}},nextTick () {// 记录最终滑动距离this.temporaryData.lastPosWidth = this.temporaryData.poswidththis.temporaryData.lastPosHeight = this.temporaryData.posheightthis.temporaryData.lastRotate = this.temporaryData.rotatethis.temporaryData.lastZindex = 20// 循环currentPagethis.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this.temporaryData.currentPage + 1// currentPage切换,整体dom进行变化,把第一层滑动置最低this.$nextTick(() => {this.temporaryData.poswidth = 0this.temporaryData.posheight = 0this.temporaryData.opacity = 1this.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 = truethis.temporaryData.lastPosWidth = 0this.temporaryData.lastPosHeight = 0this.temporaryData.lastOpacity = 0this.temporaryData.lastRotate = 0this.temporaryData.swipe = falsethis.temporaryData.lastZindex = -1}},prev () {this.temporaryData.tracking = falsethis.temporaryData.animation = true// 计算划出后最终位置let width = this.$el.offsetWidththis.temporaryData.poswidth = -widththis.temporaryData.posheight = 0this.temporaryData.opacity = 0this.temporaryData.rotate = '-3'this.temporaryData.swipe = truethis.nextTick()},next () {this.temporaryData.tracking = falsethis.temporaryData.animation = true// 计算划出后最终位置let width = this.$el.offsetWidththis.temporaryData.poswidth = widththis.temporaryData.posheight = 0this.temporaryData.opacity = 0this.temporaryData.rotate = '3'this.temporaryData.swipe = truethis.nextTick()},rotateDirection () {if (this.temporaryData.poswidth <= 0) {return -1} else {return 1}},angleRatio () {let height = this.$el.offsetHeightlet offsetY = this.temporaryData.offsetYlet ratio = -1 * (2 * offsetY / height - 1)return ratio || 0},inStack (index, currentPage) {let stack = []let visible = this.temporaryData.visiblelet length = this.pages.lengthfor (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.currentPagelet length = this.pages.lengthlet lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1let style = {}let visible = this.temporaryData.visibleif (index === this.temporaryData.currentPage) {return}if (this.inStack(index, currentPage)) {let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + lengthstyle['opacity'] = '1'style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')'style['zIndex'] = visible - perIndexif (!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.lastOpacitystyle['zIndex'] = this.temporaryData.lastZindexstyle['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.opacitystyle['zIndex'] = 10if (this.temporaryData.animation) {style['transitionTimingFunction'] = 'ease'style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'}return style}},}}
</script>

由于组件处理了touchmouse事件,所以支持移动端和PC端滑动。

另外,点击卡片会跳转到卡片详细页。

okey,基于Nuxt.js|Vue.js实现仿探探卡片滑动效果就分享到这里。

最后附上一个最近项目实例

react+redux仿微信web版聊天实例|react pc端聊天室

Vue/Nuxt.js仿Tinder|探探翻牌特效|vue仿探探卡片滑动相关推荐

  1. 老弟,来了?VUE+Nuxt.js+Koa+Vuex入门教程(一)仿写一个cnode网站

    if(有工作){if(工作地址 == "深圳" || 工作地址 == "广州" ){do(请联系作者,qq:1172081598)} } 何为Nuxt.js N ...

  2. android仿陌陌tab,Vue|Nuxt.js仿探探卡片式左右拖拽|vue仿Tinder

    开场技术宅男对探探/陌陌并不陌生,一款专注于陌生人的社交App.里面的左右滑动翻牌子效果更是让人眼前一亮,似乎有一种古时君王选妃子的感觉.让人玩的爱不释手. 一睹风采 哈哈,效果还行吧.下面就简单的讲 ...

  3. JS+CSS实现漂亮的日历特效(仿win10系统日历)

    初衷:无意见发现win10的系统日鼠标悬浮的特效非常好看,想尝试一下用CSS和JS实现一下. 先上一下目前实现的效果图: 难点是这种间隔效果和鼠标移动效果的实现,如下图 间隔效果的实现我采用了 bac ...

  4. android 滑动接听源码,android仿摩拜单车APP、炫酷RecyclerView、卡片滑动、仿饿了么点餐、自定义索引等源码...

    Android精选源码 Android优质博客 前言permissions4m 最初的设计是仅仅做成一个编译器注解框架,在1.0.0版本时,它纯粹地实现了原生 Android 请求流程,关于它的设计思 ...

  5. android 卡片上滑放大,android仿摩拜单车APP、炫酷RecyclerView、卡片滑动、仿饿了么点餐、自定义索引等源码...

    Android精选源码 Android优质博客 前言permissions4m 最初的设计是仅仅做成一个编译器注解框架,在1.0.0版本时,它纯粹地实现了原生 Android 请求流程,关于它的设计思 ...

  6. html5做探探,基于Nuxt.js+Vue聊天实例|nuxt仿微信/探探聊天界面

    1.项目简介 Nuxt.js是目前比较热门的服务端渲染SSR框架.凭借其更好的SEO.更快的内容到达时间(*首屏渲染速度快*) 加之基于Vue.js技术开发,更易于上手,获得了很多技术开发者的青睐. ...

  7. 使用Nuxt.js搭建VUE应用的SSR(服务端渲染)

    Nuxt.js的介绍 Nuxt.js概述 nuxt.js简单的说是Vue.js的通用框架,最常用的就是用来作SSR(服务器端渲染) Vue.js是开发SPA(单页应用)的,Nuxt.js这个框架,用V ...

  8. 开始使用Nuxt.js

    Nuxt.js简单的说是Vue.js的通用框架,最常用的就是用来作SSR(Server Side Render服务端渲染).再直白点说,就是Vue.js原来是开发SPA(Single Page App ...

  9. 使用Nuxt.js框架开发(SSR)服务端渲染项目

    (SSR)服务端渲染的优缺点 优点: 1.前端耗时少,首屏加载速度快.因为后端拼接完了html,浏览器只需要直接渲染出来. 2.有利于SEO.因为在后端有完整的html页面,所以爬虫更容易爬取获得信息 ...

最新文章

  1. Python可视化——3D绘图解决方案pyecharts、matplotlib、openpyxl
  2. jsp页面取整数 和Java页面取整数
  3. 产品策略研究期的数据分析与挖掘
  4. c语言结构体复习笔记
  5. 动态卷积超进化!通道融合替换注意力,减少75%参数量且性能显著提升 | ICLR 2021
  6. Web渗透测试(sql注入 access,mssql,mysql,oracle,)
  7. java中toarray()的 用法_java容器中toArray的用法
  8. tcpdump抓两个网卡的包_tcpdump 抓包统计 分享助,请问用tcpdump抓包后,如何查看每个包...
  9. 怎么用c语言实现万年历,用C语言如何编写“万年历”
  10. css 涟漪,CSS3水波涟漪动画定位样式如何制作
  11. Java项目的命名规范
  12. UE4 蓝图教程(三) 材质
  13. java timer 销毁_java.util.Timer用法须知
  14. graphql_GraphQL简介
  15. 【最全最详细】publiccms使用教程
  16. 为年会写的配乐诗朗诵稿
  17. 感芯科技MC3172移植U8g2图形库
  18. 【TensorFlow】神经网络中间层截取、可视化中间层结果
  19. 微软校招2015 Beautiful String
  20. Excel小技巧(随机点名)

热门文章

  1. J2EE基础教程(2):JSP初探
  2. 打开PhotoshopTea的正确姿势
  3. 图像处理: Canny边缘检测
  4. linux动态调频响应时间,CPU动态调频(linux 3.14.0)一
  5. 一次性搞懂 HTTP、HTTPS、SPDY、HTTP2
  6. linux服务器安装xfce,Ubuntu 16.10安装Xfce桌面与VNC远程连接
  7. PyMC3 - GLM之线性回归
  8. Search(搜索法)
  9. Unrecognized SSL message, plaintext connection?
  10. python 制作下雪的情景