Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

类似Tinder探探的卡片效果的组件,社区中已经非常多了。我这一版除了可以实现和他们一样的效果外。还增加了飞卡的效果,就是类似我的女神邱淑贞这样。

可以将卡片朝任意拖拽方向飞出去,必须得是不是!

下面来说说我的实现思路

绝对原创,如有雷同,纯属巧合

先叠起来

其实最早实现这个效果,我是在 2018 年的时候,在 weex 上实现过一版。原帖请看这里:https://zhuanlan.zhihu.com/p/37482853

咱们这次不要求在 weex 里可用,所以叠起来非常简单。

首先让三个卡片按照近大远小的原则分别设置设置z-index,宽和高,比如每一层卡片的宽和高比上一层卡片要缩小 20 个像素(还有一种做法是通过 zoom 或者 scale 来设置远处卡片的缩小级别)。然后加入绝对定位position:absolutez-index就可以将卡片层叠起来了。

拖动第一张卡

因为只有第一张卡片可以拖动,所以我们只要监听第一张卡片的touchstart,touchmove,touchcancel,touchend四个事件即可。

拖动的时候需要注意,在touchstart的时候记录一下手指按下的位置,在touchmove时要减去这个位置,看上去就是点哪儿从哪儿拖。

touchStart:function(e){ var curTouch=e.touches[0]; this.startLeft=curTouch.clientX-this.left; this.startTop=curTouch.clientY-this.top;}touchMove:function(e){ var curTouch=e.touches[0]; this.left=curTouch.clientX-this.startLeft; this.top=curTouch.clientY-this.startTop;}

飞出去

要实现这个效果,需要用到一些数学公式。

计算卡片当前拖拽的坐标和起始坐标的夹角

var angle=Math.atan2((当前坐标.y-起点坐标.y), (当前坐标.x-起点坐标.x));

飞出去的落点x轴坐标通过计算 angle 的余弦值再乘以力度得出

this.left=Math.cos(angle)*this.throwDistance;

飞出去的落点y轴坐标通过计算 angle 的正弦值再乘以力度得出

this.top=Math.sin(angle)*this.throwDistance;

这里咱们做得再完善一些,在拖动结束时去判断一下当前拖动的距离是否足够触发飞卡效果。如果不触发飞卡效果,则触发回位效果。这样的话也可以防止用户误操作。

//计算两点之间的直线距离getDistance:function(x1, y1, x2, y2) {    var _x = Math.abs(x1 - x2);     var _y = Math.abs(y1 - y2);     return Math.sqrt(_x * _x + _y * _y);}            var distance=this.getDistance(0,0,this.left,this.top);if(distance>this.throwTriggerDistance){    this.makeCardThrow();}else{    this.makeCardBack();}

下层的卡片上推

上推其实很简单,一开始的时候,我就定义了四张(不是3张吗?怎么变4张了)卡片的大小和位置。

当第一张卡飞出去后

  • 第 2 张卡片变更为原本第 1 张卡片的位置和大小
this.width2=this.cardWidth;this.height2=this.cardHeight;this.left2=0;this.top2=0;
  • 第 3 张卡片变更为原本第 2 张卡片的位置和大小
this.width3=(this.cardWidth-this.leftPad*2);this.height3=(this.cardHeight-this.topPad*2);this.left3=this.leftPad;this.top3=(this.topPad*3);
  • 第 4 张卡片原本是透明的,现在变为第 3 张卡片的位置和大小
this.width4=(this.cardWidth-this.leftPad*4);this.height4=(this.cardHeight-this.topPad*4);this.left4=this.leftPad*2;this.top4=(this.topPad*6);this.opacity4=1;

我把阴影效果先去掉,大家观察一下这个细节

重置所有卡片

底层的卡片上推和第一张卡片的飞出效果是同时进行的,由 css 的transition来控制。不过时间是我们设定好的,所以只要在上推和飞出的动画时间结束后,我们重置一下所有 4 张卡片的大小和位置即可。

this.onThrowStart();setTimeout(function(){    that.isThrow=false;    that.isAnimating=false;    that.onThrowDone();    that.resetAllCard();},400);

这里需要注意,所有四张卡片都需要瞬间完成重置,所以这步之前应该禁用掉 transition 动画。

组件化

为了适应各种使用场景,我们要将这个效果封装一下。

//提供几个事件,分别是拖动时,拖动结束,飞卡结束,飞卡失败(回位)@onDragMove='onCardDragMove'@onDragStop='onCardDragStop'@onThrowDone='onCardThrowDone'@onThrowFail='onCardThrowFail'//参数就不细说了,都能看明白:cardWidth="200" :cardHeight="200"cardBgColor="#fff":leftPad="10":topPad="6":borderRadius="8":throwTriggerDistance="100"dragDirection="all":hasShadow="false":hasBorder="true"

提供三个 slot,你可以非常方便的往卡片里塞内容

//firstCard,secondCard,thirdCard<slot ></slot>

现在来模仿几个效果

某乎的推荐回答

@onDragMove='onCardDragMove'@onDragStop='onCardDragStop'@onThrowDone='onCardThrowDone':cardWidth="300" :cardHeight="120":throwTriggerDistance="100"dragDirection="horizontal":hasShadow="true"

仅允许水平拖动

由于改变宽高会导致文字换行变化,也许卡片用缩放会更好一些吧

探探的效果

实现探探效果的核心是监听卡片拖动的位置

onCardDragMove(obj){    if(obj.left<-10){        this.action;    }else if(obj.left>10){        this.action;    }else{        this.action;    }}

源码仓库

https://github.com/ezshine/ezflycard

之后抽空再实现一个 vue3 的版本吧~

关注大帅搞全栈

欢迎拍砖,一起探讨更优雅的实现