前言随着Hybrid应用的丰富,HTML5工程师不再满足于简单地将桌面体验移植到移动端。自带丰富的手势系统。HTML5并没有提供开箱即用的手势系统,而是提供了一个更底层的触摸事件监控。基于此,我们可以制作自己的手势库。手势常用的HTML5手势可以分为两类,单点手势和两点手势。单点手势包括tap(点击)、doubletap(双击)、longtap(长按)、swipe(波浪)和move(移动)。两点手势包括捏合(缩放)和旋转(旋转)。接下来,我们实现一个检测这些手势的javaScript库,并使用这个手势库来制作炫酷的交互效果。我们不会在这里详细介绍移动手势检测。综上所述,每发生一次touchmove事件,减去两个位移点之间的坐标位置即可。点击(tap)手势检测的关键是利用touchstart、touchmove、touchend三个事件对手势进行分解。那么如何分解点击事件呢?touchstart时进入点击检测,只有一个触摸点。因为点击事件仅限于一根手指的移动。touchmove事件没有发生或者touchmove在一个小范围内(如下图)。将touchmove限制在一个很小的范围内是为了给用户一定的冗余度,因为不能保证用户的手指在触摸屏幕时不会有轻微的移动。touchend发生在touchstart之后的很短时间内(如下图)。该时间段的阈值以毫秒为单位,用于限制手指接触屏幕的时间。因为点击事件从头到尾都很快。通过以上流程,就可以开始实现点击事件监听了。_getTime(){returnnewDate().getTime();}_onTouchStart(e){//记录touch的起始位置this.startX=e.touches[0].pageX;this.startY=e.touches[0]。pageY;if(e.touches.length>1){//多点监听...}else{//记录touch开始时间this.startTime=this._getTime();}}_onTouchMove(e){...//记录手指移动的位置this.moveX=e.touches[0].pageX;this.moveY=e.touches[0].pageY;...}_onTouchEnd(e){lettimestamp=this._getTime();if(this.moveX!==null&&Math.abs(this.moveX-this.startX)>10||this.moveY!==null&&Math.abs(this.moveY-this.startY)>10){...}else{//手指移动的位移应小于10个像素,手指与屏幕的接触时间应为500毫秒if(timestamp-this.startTime<500){this._emitEvent('onTap')}}}双击(doubletap)和单击一样,双击事件也需要我们对手势进行量化和分解。双击事件是手指动作。所以在touchstart的时候,我们需要判断此时屏幕有多少个接触点。一个双击事件包含两个独立的点击行为。理想情况下,两次点击应落在屏幕上的同一位置。为了给用户一定的冗余空间,两次点击的坐标点之间的距离限制在10个像素以内。双击事件本质上是两次快速点击。也就是说,两次点击之间的时间很短。通过一定的测试量化后,我们将两次点击的时间间隔设置为300毫秒。注意,在双击事件中,我们检测了相邻两个touchstart事件的位移和时间间隔。_onTouchStart(e){if(e.touches.length>1){...}else{if(this.previousTouchPoint){//相邻两个touchstart之间的距离要小于10,时间间隔要小超过300msif(Math.abs(this.startX-this.previousTouchPoint.startX)<10&&Math.abs(this.startY-this.previousTouchPoint.startY)<10&&Math.abs(this.startTime-this.previousTouchTime)<300){this._emitEvent('onDoubleTap');}}//保存最后一次touchstart的时间和位置信息this.previousTouchTime=this.startTime;this.previousTouchPoint={startX:this.startX,startY:this.startY};}}长按(longpress)长按应该是最容易分解的手势了。我们可以这样分解:在touchstart发生后的很长一段时间内,如果没有发生touchmove或者touchend事件,那么就会触发长按手势。长按是一根手指的行为,需要检测屏幕上是否只有一个接触点。如果手指在空间上移动,则取消长按事件。如果手指在屏幕上停留时间超过800ms,则触发长按手势。如果手指停留在屏幕上的时间小于800ms,即touchend发生后800ms内触发touchend,则取消长按事件。_onTouchStart(e){clearTimeout(this.longPressTimeout);if(e.touches.length>1){}else{this.longPressTimeout=setTimeout(()=>{this._emitEvent('onLongPress');});}}_onTouchMove(e){...clearTimeout(this.longPressTimeout);...}_onTouchEnd(e){...clearTimeout(this.longPressTimeout);...}缩放(捏)缩放是一个很有趣的手势,还记得当您第一次在第一部iPhone上捏捏缩放图像时有多震惊吗?即便如此,捏合手势的检测还是比较简单的。缩放是双指行为,需要检测屏幕上是否有两个接触点。缩放比例的量化是通过两个缩放行为之间的距离的比值得到的,如下图所示。所以缩放的核心是得到两个接触点之间的直线距离。//勾股定理_getDistance(xLen,yLen){returnMath.sqrt(xLen*xLen+yLen*yLen);}这里xLen是两个接触点x坐标差的绝对值,yLen对应y-坐标差的绝对值。_onTouchStart(e){if(e.touches.length>1){letpoint1=e.touches[0];letpoint2=e.touches[1];letxLen=Math.abs(point2.pageX-point1.pageX);letyLen=Math.abs(point2.pageY-point1.pageY);this.touchDistance=this._getDistance(xLen,yLen);}else{...}}在_onTouchStart函数中获取并保存touchstart时的两个触点之间的距离。_onTouchMove(e){if(e.touches.length>1){letxLen=Math.abs(e.touches[0].pageX-e.touches[1].pageX);letyLen=Math.abs(e.touches[1].pageY-e.touches[1].pageY);lettouchDistance=this._getDistance(xLen,yLen);if(this.touchDistance){letpinchScale=touchDistance/this.touchDistance;this._emitEvent('onPinch',{scale:pinchScale-this.previousPinchScale});this.previousPinchScale=pinchScale;}}else{...}}旋转(rotate)旋转手势需要检测两个比较重要的值,一个是旋转的角度,另一个是旋转方向(顺时针或逆时针)。旋转角度和方向的计算需要通过向量的计算得到,本文不再展开。首先,你需要得到向量的旋转方向和角度。//这两个方法属于向量计算。具体原理请阅读本文中的参考资料***(vector1,vector2);direction=direction>0?-1:1;letlen1=this._getDistance(vector1.x,vector1.y);letlen2=this._getDistance(vector2.x,vector2.y);letmr=len1*len2;if(mr===0)return0;letdot=vector1.x*vector2.x+vector1.y*vector2.y;letr=dot/mr;if(r>1)r=1;if(r<-1)r=-1;returnMath.acos(r)*direction*180/Math.PI;}然后,当手指移动时,我们调用方法获取旋转方向和角度。_onTouchStart(e){...if(e.touches.length>1){this.touchVector={x:point2.pageX-this.startX,y:point2.pageY-this.startY};}...}_onTouchMove(e){...if(this.touchVector){letvector={x:e.touches[1].pageX-e.touches[0].pageX,y:e.touches[1].pageY-e.touches[0].pageY};letangle=this._getRotateAngle(vector,this.touchVector);this._emitEvent('onRotate',{angle});this.touchVector.x=vector.x;this.touchVector.y=vector.y;}...}实战到此,我们的手势系统就完成了。接下来我们要测试这个系统在实战中是否靠谱,做一个简单的支持图片缩放、旋转、移动、长按的图片浏览器。首先,做好DOM规划。和“之前”一样,我们的事件监听机制并不直接作用于图片,而是作用于图片的父元素。然后,就可以开始使用上面的手势检测系统了。render(){return(
