简介
AlloyFinger 是由腾讯前端团队 AlloyTeam 出品的一个小巧轻量级的移动端手势库,整个手势库的代码不超过400行,却支持绝大多数的手势操作,能够满足日常的开发需求。AlloyFinger传送门: AlloyFinger。
JavaScript 移动端触摸事件
手机移动端浏览器提供了4种触摸事件:touchstart,touchmove,touchend,touchcancel,分别对应的是手指触点刚接触屏幕时触发事件,手指触点在屏幕上移动时触发事件,手指触点移开屏幕时触发事件以及被系统中断时触发事件(按 Home 键返回主屏等操作)。
这里要说明下,移动端浏览器也支持部分 PC 端带有的事件,比如 click 事件。但是在移动端上,click 事件会存在延时触发的情况,大概延时300ms。
移动端300ms延时触发 click 事件
在移动端为什么click事件会存在延时触发的情况呢?究其原因,是因为苹果公司在早期发布iphone的时候,采用了双击缩放网页的设计。当用户手指点击一次屏幕时,浏览器不能立即判定用户操作是单击操作还是双击操作,而是延迟了300ms,以判断用户是否再次点击了屏幕,如果300ms之内没有再次点击屏幕就判定为单击事件,才会去触发click事件。
源码分析
AlloyTeam 团队为 AlloyFinger 打造了多个能够适用不同技术栈中的手势库版本,能够方便的使用在 React 框架,Vue框架以及原生JS中。不同场景下的手势库版本的实现思路都是一样的,所以这里只分析了原生JS的实现思路。
如何使用
AlloyFinger 的使用方式非常简单,源码中暴露出了一个全局的 AlloyFinger 构造函数对象,使用方式如下,返回值是一个 AlloyFinger 实例对象。
// element 是需要手势操作的DOM元素,值可以是DOM对象也可以是元素选择器。
// options 是一个对象,包含了需要的手势操作函数。
var af = new AlloyFinger(element, options);
var af = new AlloyFinger(element, {
tap: function() {
//do something...
}
});
复制代码
有了 AlloyFinger 实例对象后,你还可以通过绑定自定义事件的方式使用手势库
//绑定手势事件
af.on('tap', function() {
//do something...
});
//解绑手势事件
af.off('tap', function() {
//do something...
});
//销毁实例
af.destroy();
复制代码
整体架构
AlloyFinger 构造函数
首先,先定义了一个 AlloyFinger 构造函数,里面做了很多操作,事件的监听回调,变量值的初始化,将手势操作作为订阅者添加到订阅列表中。在这部分源码中,会初始化很多关于手指触点的水平坐标和垂直坐标的存储变量,刚开始看的时候会觉得代码比较的混乱,所以笔者把这部分的变量捋一遍梳理了出来,便于清晰的阅读源码。
this.x1: 存储在刚开始触摸时第一个手指触点的X坐标位置
this.y1: 存储在刚开始触摸时第一个手指触点的Y坐标位置
this.preV.x: 存储第一个手指触点与第二个手指触点之间的水平间距
this.preV.y: 存储第一个手指触点与第二个手指触点之间的垂直间距
this.x2: 存储在移动操作时第一个手指触点的X坐标位置
this.y2: 存储在移动操作时第一个手指触点的Y坐标位置
this.sx2: 存储在移动操作时第二个手指触点的X坐标位置
this.sy2: 存储在移动操作时第二个手指触点的Y坐标位置
复制代码
整个的源码解读都放置在我的github上,几乎每一行都有自己的注解,感兴趣的话可以点击这里:传送门。
源码都是精简干练的,多看优秀的源码还是对自己的技术有帮助的,可能看完了之后会思考自己怎么去DIY一个手势库呢?想要自己怎么去DIY一个手势库,必须得先了解各个手势操作的实现思路,有思路了之后才能动手写代码。
具体实现
- tap点击
tap的本质其实就是touchend,但是在具体实现的时候必须做下限制,当前只存在一个手指触点,且touchstart的时候手指触点和touchend时手指触点的X轴Y轴的偏差不能小于30,这样才能判定当前的操作是tap操作。
var len = evt.touches.length;
if(len < 1) {
if ((this.x2 && Math.abs(this.x1 - this.x2) <= 30) ||
(this.y2 && Math.abs(this.y1 - this.y2) <= 30)) {
//我是tap操作,do something...
}
}
复制代码
- doubleTap
doubleTap双击操作的实现思路大致是这样的,得先判断一段时间内是否有两次touchstart操作,并且两次touchstart都是快速完成的,不然会被认为是长按操作了,还有一点就是两次触点的位置的X轴Y轴的偏差不能小于30。
//存储手指按下触摸操作的时间戳
this.now = null;
//存储上一次手指触点触摸的时间戳
this.last = null;
//用于存储手指触摸操作时的水平坐标和垂直坐标(如果是多指触摸操作,则记录的是第一个手指触摸的位置)
this.preTapPosition = { x: null, y: null };
//是否为双击操作
this.isDoubleTap = false;
...
function start() {
this.now = Date.now();
if (this.preTapPosition.x !== null) {
//如果手指连续触摸操作之间的时间间隔小于250毫秒,且手指连续触摸操作之间的触点位置水平坐标小于30,垂直坐标小于30,那么就判定该操作为双击操作
this.isDoubleTap = (this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30);
}
this.preTapPosition.x = this.x1;
this.preTapPosition.y = this.y1;
this.last = this.now;
}
function end() {
if (this.isDoubleTap) {
//我是doubleTap操作,do something...
}
}
复制代码
- swipe
swipe滑过操作具体的实现思路是touchstart的手指触点的坐标和touchend时候手指触点的坐标x、y方向偏移要大于30,且还要判断是往哪个方向滑动。
//判定swipe滑动的方向
_swipeDirection: function (x1, x2, y1, y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
}
复制代码
更多的手势操作源码分析可以参考我的github上的源码分析,传送门。AlloyFinger 手势库还用在了一个小巧的移动端裁剪图片工具上,下次还可以分析一波裁剪工具 AlloyCrop 的源码,学习到裁剪图片的原理和实现方案,平时在开发过程中,其实只要清楚了实现思路和原理,就能够方便的实现具体的功能。