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

一睹风采

哈哈,效果还行吧。下面就简单的讲解下具体的实现方法。

页面布局

页面整体分为 顶部Navbar、卡片区域、底部Tabbar 三个部分。

遇见TA

侧边弹出框

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

侧边弹窗模板

范围

{{distanceVal}}

自动增加范围

性别

女生

好评度

{{starVal}}星

优先在线用户

优先新用户

更新

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;

},

// ...

},

}

仿探探翻牌子

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

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

pages数据格式module.exports = [

{

avatar: '/assets/img/avatar02.jpg',

name: '放荡不羁爱自由',

sex: 'female',

age: 23,

starsign: '天秤座',

distance: '艺术/健身',

photos: [...],

sign: '交个朋友,非诚勿扰'

},

...

]

flipcard组件模板

  • @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)"

    @transitiοnend="onTransitionEnd(index)"

    >

    {{item.name}}

    {{item.age}}

    {{item.starsign}}

    {{item.distance}}

/**

* @Desc Vue仿探探|Tinder卡片滑动FlipCard

* @Time andy by 2020-10-06

* @About Q:282310962 wx:xy190310

*/

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

let height = this.$el.offsetHeight

let offsetWidth = width - Math.abs(this.temporaryData.poswidth)

let offsetHeight = height - Math.abs(this.temporaryData.posheight)

let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0

return ratio > 1 ? 1 : ratio

},

// 划出宽度比例

offsetWidthRatio () {

let width = this.$el.offsetWidth

let offsetWidth = width - Math.abs(this.temporaryData.poswidth)

let ratio = 1 - offsetWidth / width || 0

return ratio

}

},

methods: {

touchstart (e) {

if (this.temporaryData.tracking) {

return

}

// 是否为touch

if (e.type === 'touchstart') {

if (e.touches.length > 1) {

this.temporaryData.tracking = false

return

} else {

// 记录起始位置

this.basicdata.start.t = new Date().getTime()

this.basicdata.start.x = e.targetTouches[0].clientX

this.basicdata.start.y = e.targetTouches[0].clientY

this.basicdata.end.x = e.targetTouches[0].clientX

this.basicdata.end.y = e.targetTouches[0].clientY

// offsetY在touch事件中没有,只能自己计算

this.temporaryData.offsetY = e.targetTouches[0].pageY - this.$el.offsetParent.offsetTop

}

// pc操作

} else {

this.basicdata.start.t = new Date().getTime()

this.basicdata.start.x = e.clientX

this.basicdata.start.y = e.clientY

this.basicdata.end.x = e.clientX

this.basicdata.end.y = e.clientY

this.temporaryData.offsetY = e.offsetY

}

this.temporaryData.isStackClick = true

this.temporaryData.tracking = true

this.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].clientX

this.basicdata.end.y = e.targetTouches[0].clientY

} else {

e.preventDefault()

this.basicdata.end.x = e.clientX

this.basicdata.end.y = e.clientY

}

// 计算滑动值

this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x

this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y

let rotateDirection = this.rotateDirection()

let angleRatio = this.angleRatio()

this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio

}

},

touchend (e, index) {

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

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

this.temporaryData.poswidth = -width

this.temporaryData.posheight = 0

this.temporaryData.opacity = 0

this.temporaryData.rotate = '-3'

this.temporaryData.swipe = true

this.nextTick()

},

next () {

this.temporaryData.tracking = false

this.temporaryData.animation = true

// 计算划出后最终位置

let width = this.$el.offsetWidth

this.temporaryData.poswidth = width

this.temporaryData.posheight = 0

this.temporaryData.opacity = 0

this.temporaryData.rotate = '3'

this.temporaryData.swipe = true

this.nextTick()

},

rotateDirection () {

if (this.temporaryData.poswidth <= 0) {

return -1

} else {

return 1

}

},

angleRatio () {

let height = this.$el.offsetHeight

let offsetY = this.temporaryData.offsetY

let ratio = -1 * (2 * offsetY / height - 1)

return ratio || 0

},

inStack (index, currentPage) {

let stack = []

let visible = this.temporaryData.visible

let length = this.pages.length

for (let i = 0; i < visible; i++) {

if (currentPage + i < length) {

stack.push(currentPage + i)

} else {

stack.push(currentPage + i - length)

}

}

return stack.indexOf(index) >= 0

},

// 非首页样式切换

transform (index) {

let currentPage = this.temporaryData.currentPage

let length = this.pages.length

let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1

let style = {}

let visible = this.temporaryData.visible

if (index === this.temporaryData.currentPage) {

return

}

if (this.inStack(index, currentPage)) {

let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length

style['opacity'] = '1'

style['transform'] = 'translate3D(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

}

},

}

}

组件支持touch和mouse事件,在移动端和PC端均可滑动。

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

好了,基于Vue实现探探卡片效果就分享到这里。希望能喜欢~~ ✍

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

  1. 多款顶级好用的 Vue 表单设计器测评推荐,可拖拽生成表单

    本文完整版:<多款顶级好用的 Vue 表单设计器测评推荐,可拖拽生成表单> Vue 表单设计器 form-generator - 适配 Element Plus UI 框架的表单设计器 f ...

  2. JS组件系列——Bootstrap Table 表格行拖拽(二:多行拖拽)

    原文:JS组件系列--Bootstrap Table 表格行拖拽(二:多行拖拽) 前言:前天刚写了篇JS组件系列--Bootstrap Table 表格行拖拽,今天接到新的需要,需要在之前表格行拖拽的 ...

  3. JS组件系列——Bootstrap Table 表格行拖拽

    JS组件系列--Bootstrap Table 表格行拖拽 原文:JS组件系列--Bootstrap Table 表格行拖拽 前言:之前一直在研究DDD相关知识,好久没更新JS系列文章了.这两天做了一 ...

  4. Vue/Nuxt.js仿Tinder|探探翻牌特效|vue仿探探卡片滑动

    基于Vue.js|Nuxt.js实现探探卡片滑动切换效果 陌陌|探探社交App中拖拽滑动翻牌子效果让人印象深刻,最近在开发Nuxt项目,需要实现类似这个效果,于是经过多次调试,最终实现了,现整理作些简 ...

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

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

  6. Android仿探探卡片拖拽,Vue 仿探探拖拽卡片的效果

    原标题:Vue 仿探探拖拽卡片的效果 已更新Vue3版,请给前端大全发送关键字vue3仿探探获取Vue3版 类似 Tinder 和 探探 的卡片效果的组件,社区中已经非常多了.我这一版除了可以实现和他 ...

  7. vue js 可随意拖动盒子 以及禁止拖拽

    可拖动弹窗: 1.新建一个js,放置如下js代码 import Vue from 'vue'; //使用Vue.directive()定义一个全局指令 //1.参数一:指令的名称,定义时指令前面不需要 ...

  8. vue手势滚动_vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果

    https://github.com/383514580/any-touch 先看demo demo 说点湿的 iscroll其实代码量挺大的(近2100行, 还有另一个类似的库betterScrol ...

  9. 安卓开发仿微信图片拖拽_仿微信朋友圈发表图片拖拽和删除功能

    原标题:仿微信朋友圈发表图片拖拽和删除功能 中国联通在香港公布了上市公司2017年中期业绩.2017年上半年,公司主要业绩指标持续向好,收入稳步回升,服务收入达到人民币1,241.1亿元,同比增长3. ...

最新文章

  1. 自己动手写一个能操作redis的客户端
  2. Oracle用户密码过期和用户被锁解决方法【转】
  3. 浅析Microsoft .net PetShop程序中的购物车和订单处理模块(Profile技术,异步MSMQ消息)转...
  4. 【阿里云API】 阿里云API调用的若干说明
  5. CTFshow 命令执行 web44
  6. bzoj3895: 取石子(博弈论,记忆化搜索)
  7. 最近找工作面的面试题目汇总(一)
  8. 第一百五十二期:白话Entity Framework Core数据验证
  9. Delphi 中的字符串函数(6) - StrUtils 中的 Ansi 字符串函数
  10. django+nginx+uwsgi项目部署文档整理
  11. 复合梯形公式与复合辛普森公式matlab_时尚女装套装的公式图纸分享
  12. html框架集frame是啥意思,HTML框架集frameset和内嵌框架iframe
  13. 校园网如何实现网络共享
  14. C++: decay关键字的作用
  15. 华为员工工资曝光:入职12年月薪31万,小编我瑟瑟发抖
  16. ftc文件_美国参议员指责FTC拒绝收集防病毒数据
  17. Arthas Spring Boot Starter工程启动报错
  18. 机器自动翻译古文拼音 - 十大宋词 - 青玉案 凌波不过横塘路 贺铸
  19. [实训题目EmoProfo]基于深度学习的表情识别服务搭建(一)
  20. 数据分析指标体系搭建实战!

热门文章

  1. 正则表达式 捕获分组的理解
  2. 关于spring boot的Bean named ‘aaa‘ is expected to be of type ‘bbb‘ but was actually of type ‘bbb‘问题的解决方案
  3. 211北京科技大学,计算机专硕考研变难了!
  4. 阿里云+tp5.1 实现语音合成(文字转音频)
  5. 顾比倒数线——出入场管理的概述与解读
  6. QVTKWidget控件显示二维图片
  7. AndroidStudio生成MD5、SHA1
  8. MOS管和IGBT管有什么区别?
  9. NB-IoT标准演进R13-R14
  10. HashMap 21 问!