移动端手势事件浅析

匆走过了2016,新的一年又开始了。新年没有什么计划,因为往年欠了太多,所以,边走边看吧。不过,新年第一天唯一值得说的事就是鼓足了勇气主动去认识了一个美女(由于本人实在太内向,以往基本没这样做过),也算是自我突破一下吧。
之前有个朋友问过一关于移动端长按怎么实现的问题,为了应急就给了一个很丑的方法,凑合着把功能做了,之后一直想整理一下关于移动端的手势操作,也一直没时间,但是元旦小长假,这种专门给我这种年轻屌丝冬眠的假期,我却用它来写了这篇博客,真是罪过。这篇博客开写之前我先去做了个各种手势操作的demo,当然,主要参考了鹅厂一个前端团队的框架,整个demo做的我各种蛋疼,可能是在住处闷了太久,再加上换了个mac写代码各种不适应,导致心理很急躁,拖了比较久才写完demo,可以猛搓这里看效果,当然建议最好用微信之类的扫一扫下面二维码(别用qq扫,因为qq对使用百度bae二级域名的这类网站会显示成危险网站,打不开),直接在手机看效果好点,pc手机模拟模式有些手势效果没法搞。

接下来,就进入正题,来聊聊移动端手势操作。移动端各种手势操作网上查了下,多的要死,五花八门的,在这里本着实用主义精神来说说其中我们经常见的一些手势,分别是长按(longTap),点击(singleTap),旋转(rotate),放大/缩小(scale),滑动(swipe),双击(doubleTap),拖动(drop),以上这些我想大家都不陌生吧,随便一个app都会有如上操作。再仔细讲之前,先来再来点甜点,也是在前文章中提到过的,对前端程序员来说,整个移动端手势操作都基于三个最基本的操作,即touchstart,touchmove,touchend。三者缺一不可,当然就这三分地,也没法缺誰。然后基于上面个三操作,我们根据用户操作习惯,又抽象分离出来了其他各种操作。接下来我们就来逐个分析常见手势。

长按(longTap)

在某些场景下,我们需要长按某个元素来实现某些功能,例如选中或者调用弹出层之类的。长按和直接单点的区别我个人觉前者是在用户非常肯定的情况下触发一些想要的功能,目的性会比较明确,后者可能是从用户习惯来考虑的(个人认识)。这种用户分析我就不扯了,还是来扯正题,要怎么用我们的touch家族三兄弟来实现呢?想想还是很简单的,长按顾名思义,就是手指长时间停留在元素上不动,直到在我们预先设定的时间点触发我们想做的事为止,这样我们就完成了一个长按操作,那么这个预先设定的时间点要在什么时候呢?这个嘛,其实我也不知道,只要条件允许,你按个一两天触发也是可以的,前提是不怕被产品打死。安全起见,我们选择比较通用的750ms左右的时间来作为长按这个事件的触发时间,这样稍微人性化一点。下面就到了铺代码的时间:

target.addEventListener('touchstart',function(e){
    //创建一个定时器,在750ms之后触发长按事件
    longTapTimer = setTimeout(function(){
        longTap(e);//长按事件自定义回调函数
    },750)
},false);
target.addEventListener('touchmove',function(e){
    //如果手指移动,则立马取消长按定时器,视为非长按操作
    clearTimeout(longTapTimer);
},false);
target.addEventListener('touchend',function(e){
    //如果手指抬起,则立马取消长按定时器
    clearTimeout(longTapTimer);
},false);

上面三步比较明了了,主要是最后一步,当我们手指抬起时,如果这个时候还没有触发我们预先定义的长按事件,那么应当立即清除掉,因为此刻或者此刻之前如果没触发,那它之后也没有触发的必要了。

点击(singleTap)

点击操作就比较单纯了,当我们手指按在元素到抬起的时候,如果没有移动或者移动的不是那么很明显的时候,则就视为点击。那么问题又来了,什么是“移动不是很明显”,这个嘛,你懂得,就看产品的爱好了。一般情况下,我们都按照安全数值来做,即就是上下左右移动距离不超过30个像素点,我们就认为是普通点击,这里有两种点击效果,一种就是我们在pc端那种,比普通触摸时间慢个200多豪秒,另一种就是抬起手指时触发的点击,这个比普通的快那么200多豪秒。具体实现如下:

target.addEventListener('touchend',function(e){
    //如果移动距离小于30像素,则触发.假设水平和竖直移动距离分别为L1,L2;
    if(L1 < 30 && L2 < 30){
        tap(e); //移动端标准点击事件
        singleTapTimer = setTimeout(function(){
            singleTap(e);//单点自定义回调,接近于click事件
        },250)
    }
},false);

上面代码没有对双击情况下,普通点击失效做处理。这个后面再说,还有一点要注意,即就是水平和垂直移动距离的计算,要记住是最后一次手指位置和最后一次的前一次的位置的差值。

双击(doubleTap)

双击事件就是普通点击的叠加,最常见的就是双击某个图片放大的功能。其触发条件为普通点击触发之前,连续两次点击,由于我们普通点击设置的出发时间为250ms,于是当两次点击时间小于250ms时我们触发双击事件,并且需要清除掉普通单击事件。实现如下

target.addEventListener('touchstart',function(e){
    //先判断当前点击时间和上次点击时间差是否在我们预先定义的250ms之内,假设时间差为:delta;
    if(delta <= 250){
        isDoubleTap = true;//此时是否为双击的状态设为true,表示满足双击条件
    }
},false);
target.addEventListener('touchend',function(e){
    //在允许触发的范围内触发双击事件
   if(L1 < 30 && L2 < 30 && isDoubleTap){
        clearTimeout(singleTapTimer);
       doubleTap(e);//双击自定义回调
       isDoubleTap = false;//将状态设为初始的状态
    }
},false);

双击只需要注意一点,在触发的时候需要一定要把普通单击给clear掉,理论上讲是水火不容的。

旋转(rotate)

旋转其实算是常用事件中较为复杂的一个事件,因为这里面牵涉到很多数学知识,当然只要你上过高中,应该都会。首先来分析下旋转的过程,当我们想要旋转某个元素时,首先两手指按住元素,然后以一定弧度吧元素旋转到想要的效果,在这个过程中,我们一直会触发touchmove事件,所以需要实时记录当前和之前一次两手指的位置。每一次touchmove事件触发时,我们需要通过两次的位置来计算旋转的角度,然后调用回调函数。那么如何计算每次的角度呢,这时该万能的数学登场了,话不多说,一图胜千言,慢慢体会下:

实在找不到什么好的画图工具,于是就用word凑合了,画完此图,我感觉我数学姿势又涨了不少。图中上半部分为实际抽象示意图,AB和A’B’分别代表了两次手指的位置,θ就是我们需要的角度,就我们目前所掌握的信息,我们应该知道A,B,A’,B’这四个点(也就是两次手指)的位置信息,换句话说,我们知道线段AB和A’B’的所有信息,那么如何利用这些信息来求出θ呢,这时为了明了一点,我把A’B’向下平移一下,变成图中下半部分灰色的A’B’,将平移后的图仍在直角坐标系中,就变成了这个样子。在图中,我们看到,θ,γ,β之间有很很明显的关系,即θ=γ-β,于是,问题又进一步转移到γ,β。到这里我们可以分别求出γ,β的正弦或者余弦,问题自然就解开了,这是一种解决办法,但是数学厉害的人可能想起了余弦定理相关的一些扩展公式(一定要珍惜你身边的这些数学厉害的人,改变世界就指望这些人了),即Cos(θ) = Cos(γ-β) = Cos(γ) Cos(β) + Sin(γ) Sin (β);最后通过js的反余弦函数求得θ。于是整个过程又明了了不少,下面就铺出一段伪代码:

target.addEventListener('touchmove',function(e){
    //记录当前和之前一次手指分别对应的竖直和水平差信息。设为:curY,curX,lastY,lastX。用上面的余弦求差方法可得到
    var cosθ = (curX*lastX + curY*lastY)/(Math.sqrt(curY*curY + curX*curX)*Math.sqrt(lastY*lastY + lastX*lastX));
    e.angle = Math.acos(cosθ);
    rotate(e);//旋转自定义回调函数
},false);

旋转关键点在于分析清楚旋转细节中信息,运用数学中三角函数相关概念来帮助计算。

放大/缩小(scale)

放大和缩小我们经常会在一些图片调整修改的时候遇到,在操作过程中,我们需要分别计算出相邻两次手指间的间距,然后求的当前和上次的比值,比值就是需要缩小放大的倍数。如图:

代码如下:

target.addEventListener('touchmove',function(e){
    //分别计算出相邻两次的长度,求的其比值。假设长度分别为L1,L2;
    e.scale = L1/L2;
    scale(e);//缩小放大自定义回调函数
},false);

滑动(swipe)

滑动元素,我们会经常在h5活动页面看到一些切屏效果或者相册浏览效果。它的判断依据主要是按下的位置信息和抬起的位置信息差值,从而得到元素滑动的方向。其中需要判断下滑动的触发点,即就是在怎么样一个差值外算是滑动了。代码如下:

target.addEventListener('touchstart',function(e){
    //记录按下的信息
    startx = e.touches[0].pageX;
    starty = e.touches[0].pageY;
},false);
target.addEventListener('touchend',function(e){
    //通过抬起时的位置信息,计算出滑动方向
    endx = e.touches[0].pageX;
    endy = e.touches[0].pageY;
    if ((Math.abs(endx - startx) > 30) || (Math.abs(endy - starty) > 30)){
        e.dir = Math.abs(endx - startx) >= Math.abs(endy - starty) ? (endx - startx > 0 ? 'Right' : 'Left') : (endy - starty > 0 ? 'Up' : 'Down');
    }
},false);

拖动(drop)

拖动元素,确切的说应该是移动元素,不要理解为拖动事件那个拖动,这里指的是元素跟随手指移动,实现比较简单,只需要计算出相邻两次移动的距离就行,代码如下:

target.addEventListener('touchmove',function(e){
    //计算出相邻两次对应坐标的差值即可,假设当前和上次分别为(curx,cury),(lastx,lasty);
    e.deltax = curx-lastx;
    e.deltay = cury-lasty;
    drop(e);//拖动自定义回调函数
},false);

以上简单介绍了下常用手势的实现方法,由于好多实现细节比较简单,所以大都以伪代码的形式给出,具体实现细节可自己完善。当然更为复杂的综合手势,可以在当前基础上将这些融合在一起,从而实现更通用的手势方法。如果对具体实现细节有兴趣可以参考开头给出的demo,里面有详尽的实现细节。另外,由于个人对知识的理解都不是很完善,所以难免会出现很多错误的观点,还望多多指教。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal 是 Java 中的一个类,它提供了一种线程局部变量的机制。线程局部变量是指每个线程都有自己的变量副本,每个线程对该变量的访问都是独立的,互不影响。 ThreadLocal 主要用于解决多线程并发访问共享变量时的线程安全问题。在多线程环境下,如果多个线程共同访问同一个变量,可能会出现竞争条件,导致数据不一致或者出现线程安全问题。通过使用 ThreadLocal,可以为每个线程提供独立的副本,从而避免了线程安全问题。 ThreadLocal 的工作原理是,每个 Thread 对象内部都维护了一个 ThreadLocalMap 对象,ThreadLocalMap 是一个 key-value 结构,其中 key 是 ThreadLocal 对象,value 是该线程对应的变量副本。当访问 ThreadLocal 的 get() 方法时,会根据当前线程获取到对应的 ThreadLocalMap 对象,并从中查找到与 ThreadLocal 对象对应的值。如果当前线程尚未设置该 ThreadLocal 对象的值,则会通过 initialValue() 方法初始化一个值,并将其存入 ThreadLocalMap 中。当访问 ThreadLocal 的 set() 方法时,会将指定的值存入当前线程对应的 ThreadLocalMap 中。 需要注意的是,ThreadLocal 并不能解决共享资源的并发访问问题,它只是提供了一种线程内部的隔离机制。在使用 ThreadLocal 时,需要注意合理地使用,避免出现内存泄漏或者数据不一致的情况。另外,由于 ThreadLocal 使用了线程的 ThreadLocalMap,因此在使用完 ThreadLocal 后,需要手动调用 remove() 方法清理对应的变量副本,以防止内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值