资讯专栏INFORMATION COLUMN

FastClick 原理解析

QiShare / 2089人阅读

摘要:一直好奇是如何工作,于是花了几天空余的时间一步步调试代码,学习。一般会在时暂停游戏存档等操作。这样即使不监听事件也能实现点击的侦听。这种情况下一定会触发事件。

Patience and perseverance will get paid.

这段时间开始实习了,在公司做hybrid,专职写js,学习到了不少东西。一直好奇fastclick是如何工作,于是花了几天空余的时间一步步调试代码,学习fastclick。这篇文章可以结合者代码看,希望可以给予需要学习fastclick的人一点思路。

有错误的地方希望指正,thk~

主流程

FastClick.attach()

FastClick(layer)

初始化化变量

this.trackingClick = false; //追踪一个click
this.trackingClickStart = 0; //追踪时间
this.targetElement = null; // 目标元素
this.touchStartX = 0;// X坐标
this.touchStartY = 0;// y坐标
this.lastTouchIndentifier = 0;
this.touchBoundary = 10;//边界条件(是否是一个点击)
this.layer = layer;//layer可以是document.body/document.documentElement

安卓设备绑定鼠标事件(在捕获阶段,为的是第一时间处理到事件)

layer.addEventListener("mouseover",bind(this.onMouse,this),true);
layer.addEventListener("mousedown",bind(this.onMouse,this),true);
layer.addEventListener("mouseup",bind(this.onMouse,this),true);

绑定touch和click事件(判定是否是click行为,取消之前的click),

//最先捕获到
layer.addEventListener("click", bind(this.onClick, this), true);
//冒泡阶段捕获
layer.addEventListener("touchstart", bind(this.onTouchStart, this), false);
layer.addEventListener("touchmove", bind(this.onTouchMove, this), false);
layer.addEventListener("touchend", bind(this.onTouchEnd, this), false);
layer.addEventListener("touchcancel", bind(this.onTouchCancel, this), false);

判断是否存在stopImmediatePropagation,如果不存在则进行hack,在onMouse中会利用stopImmediatePropagation来阻止其他点击事件的回调函数的执行,避免ghost click的现象

onMouse中,防止点透等诡异现象的代码

if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
    // Prevent any user-added listeners declared on FastClick element from being fired.
    if (event.stopImmediatePropagation) {
        event.stopImmediatePropagation();
    } else {
        // Part of the hack for browsers that don"t support Event#stopImmediatePropagation (e.g. Android 2)
        event.propagationStopped = true;
    }
    // Cancel the event
    event.stopPropagation();
    event.preventDefault();
    return false;
}

判断是否通过onclick绑定了回调函数,如果有读取出来,使用addEventListener,绑定事件处理函数

if (typeof layer.onclick === "function") {
    // Android browser on at least 3.2 requires a new reference to the function in layer.onclick
    // - the old one won"t work if passed to addEventListener directly.
    oldOnClick = layer.onclick;
    layer.addEventListener("click", function (event) {
        oldOnClick(event);
    }, false);
    layer.onclick = null;
}

触发click流程 onTouchStart()

当一些更高级别的事件发生的时候(如电话接入或者弹出信息)会取消当前的touch操作,即触发ontouchcancel。一般会在ontouchcancel时暂停游戏、存档等操作。因此在调试的时候才会在touchStart之后,就触发了touchCancel

直接断点touchend,在控制台打印,可以看到touchstart也是触发的了、

判断是不是单点触发

if (event.targetTouches.length > 1) {
    return true;
}

获取目标对象和touch事件对象

targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];

根据touch事件对象,设置一些初始属性

this.trackingClick = true; //标识跟踪该次点击
this.trackingClickStart = event.timeStamp;//点击开始的时间
this.targetElement = targetElement;//目标元素

this.touchStartX = touch.pageX; //x坐标
this.touchStartY = touch.pageY; //y坐标

onTouchMove()

如果刚触发完touchstart事件马上就触发touchend,说明手指只是轻轻点了一下屏幕,也就是所谓的点击操作。这样即使不监听click事件也能实现点击的侦听。不过这里有一个实际的情况,很多山寨的Android设备屏幕很不灵敏,需要使劲按下才能有所感知。这种情况下一定会触发touchmove事件。所以针对Android设备的点击操作可以适当放宽,比如touchstart和touchend之间可以允许有少量几个touchmove,并且touchmove的距离不能超过多少个像素等等

因此也是需要监听onTouchMove,并且加入判断

// If the touch has moved, cancel the click tracking
if ( 
    this.targetElement !== this.getTargetElementFromEventTarget(event.target) 
    || this.touchHasMoved(event)
    ) {
    this.trackingClick = false;
    this.targetElement = null;
}
onTcouhEnd()

在touchend的时候,执行this.onTouchEnd(上个流程绑定了)

判断是否在追踪该click,在this.onTouchMove的时候,如果移动的距离大于边界,则将this.trackingClick=false,在touchend就不用再判断是否为一个click的行为

if(!this.trackingClick){
    return true;
}

获取目标元素标签,需要根据标签名来做一些判断

targetTagName = targetElement.tagName.toLowerCase();

如果是label,进行bug修复

执行this.needsFocus,针对表单元素的focus和click事件的处理

先focus表单

在触发点击事件

针对IOS,滚动层bug修复

判断元素是否需要原生的click,实际上就是有些行为还是要浏览器来执行默认的行为

表单元素disabled,点击不了

type=file的控件

video

label

如果不需要,则发送一个click事件

event.preventDefault();
this.sendClick(targetElement, event);

sendClick()流程

在一些安卓设备上,必须让一个元素blured,才能使创建的clickEvent生效

if (document.activeElement && document.activeElement !== targetElement) {
    document.activeElement.blur();
}

创建clickEvent,使用touch事件对象的属性来进行初始化

clickEvent = document.createEvent("MouseEvents");
clickEvent.initMouseEvent(
        this.determineEventType(targetElement), //bug修复针对select
        true, 
        true, 
        window, 
        1, 
        touch.screenX, 
        touch.screenY, 
        touch.clientX, 
        touch.clientY, 
        false, false, false, false, 0, null);

创建完成之后,赋予对象一个额外的属性,在onClick中可以使用,然后触发点击事件,此时通过addEventListner绑定的click事件就会触发

clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);

onClick()

addEventListener添加会按照添加顺序执行

onClick作为第一个注册监听的,因此,是第一个执行的click事件的回调函数

特殊情况处理,一般不会执行

/*
    It"s possible for another FastClick-like library delivered with third-
    party code to fire a click event before FastClick does (issue #44). In 
    that case, set the click-tracking flag back to false and return early. 
    This will cause onTouchEnd to return early.
*/
if (this.trackingClick) {
    this.targetElement = null;
    this.trackingClick = false;
    return true;
}

特殊情况处理

if (event.target.type === "submit" && event.detail === 0) {
    return true;
}

执行onMouse

//创建时,附带的一个属性
if (event.forwardedTouchEvent) {
    return true;
}

最后返回为真

return permitted; //true

注意:在这里的return的true或false并不会影响绑定的其他回调函数的执行

总结

完整的看完代码,深深感觉到移动端的坑非常的多,很有怪异的现象因为没有遇到过暂时理解不了,希望之后可以继续研究,把代码完全读懂。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79839.html

相关文章

  • 2019 再聊移动端 300ms 延迟及 fastClick 原理解析

    摘要:前言最近公司新开了一条业务线,有幸和大佬们一起从头开始构建一套适合新业务的框架。俗话说得好呀,适合自己的才是最好的 前言 最近公司新开了一条业务线,有幸和大佬们一起从头开始构建一套适合新业务的框架。俗话说得好呀,适合自己的才是最好的

    skinner 评论0 收藏0
  • 2019 再聊移动端 300ms 延迟 -- 附 fastClick 原理解析

    摘要:前言最近公司新开了一条业务线,有幸和大佬们一起从头开始构建一套适合新业务的框架。俗话说得好呀,适合自己的才是最好的前言 (Foreword) 最近公司新开了一条业务线,有幸和大佬们一起从头开始构建一套适合新业务的框架。俗话说得好呀,适合自己的才是最好的

    k00baa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<