Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

移动端手势库AlloyFinger源码分析 #3

Open
webproblem opened this issue Aug 12, 2018 · 0 comments
Open

移动端手势库AlloyFinger源码分析 #3

webproblem opened this issue Aug 12, 2018 · 0 comments
Labels

Comments

@webproblem
Copy link
Owner

webproblem commented Aug 12, 2018

简介

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 的源码,学习到裁剪图片的原理和实现方案,平时在开发过程中,其实只要清楚了实现思路和原理,就能够方便的实现具体的功能。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant