zoukankan      html  css  js  c++  java
  • fastclick源码分析

    https://www.cnblogs.com/diver-blogs/p/5657323.html  地址

    fastclick.js源码解读分析

      阅读优秀的js插件和库源码,可以加深我们对web开发的理解和提高js能力,本人能力有限,只能粗略读懂一些小型插件,这里带来对fastclick源码的解读,望各位大神不吝指教~!

    fastclick诞生背景与使用

      在解读源码前,还是简单介绍下fastclick:

    诞生背景

      我们都知道,在移动端页面开发上,会出现一个问题,click事件会有300ms的延迟,这让用户感觉很不爽,感觉像是网页卡顿了一样,实际上,这是浏览器为了更好的判断用户的双击行为,移动浏览器都支持双击缩放或双击滚动的操作,比如一个链接,当用户第一次点击后,浏览器不能立刻判断用户确实要打开这个链接,还是想要进行双击的操作,因此几乎现在所有浏览器都效仿Safari当年的约定,在点击事件上加了300毫秒的延迟。

      就因为这300ms的延迟,催生了fastclick的诞生~

    使用方法

      1.引入fastclick到自己的开发环境(源码第829~840行,后面都采用简写了哈,如:829~840)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //优先兼容AMD方式
    if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
      define(function() {
        return FastClick;
      });
    else if (typeof module !== 'undefined' && module.exports) {
      //兼容commonJs风格
      module.exports = FastClick.attach;
      module.exports.FastClick = FastClick;
    else {
      //最后兼容原生Js 
      window.FastClick = FastClick;
    }  

      fastclick的引入兼容AMD、commonJs风格、原生Js的方式,在本人的大半年开发过程中,只接触过commonJs的风格,这里就不多做介绍了,根据自己项目技术栈选择吧~

      2.入口函数(824~826)

    1
    2
    3
    4
    5
    6
    //layer参数:要监听的dom对象,一般是document.body
    //options参数:用来覆盖自定义参数,个人建议不去覆盖,
    //因为里面的参数设定都是FastClick的精华,不要着急,参数在后面会详细介绍
    FastClick.attach = function(layer, options) {
      return new FastClick(layer, options);
    };

      我们如果要使用fastclick的话,只需要在自己的js上写上FastClick.attach(document.body),这样就可以了,没错,就是这么简单!

    fastclick源码解读

      

      判断是否需要调用FastClick(105~107)

      fastclick在某些情况下是不需要的,当然fastclick的开发者早已经替我们想到了,在官网上有详细的解释,如果你想详细了解,请点击这里

    1
    2
    3
    4
    //所有在不需要FastClick的浏览器会直接return掉,不会执行fastclick.js后面的代码。
    if (FastClick.notNeeded(layer)) {
      return;
    }

      参数解读(23~103)

      上面提到了入口函数中的options参数,这里不得不赞一下fastclick的源码,对每个参数都做出了详细的解释(虽然都是英文,但很容易懂),这里介绍几个我认为比较精华的参数,如下代码:

    1
    2
    3
    4
    5
    6
    //比如这几个参数,上面提到不建议自定义覆盖,
    //这些参数正是FastClick的精华所在,
    //大幅度修改数值可能让整个库的功效大打折扣。
    this.touchBoundary = options.touchBoundary || 10;
    this.tapDelay = options.tapDelay || 200;
    this.tapTimeout = options.tapTimeout || 700;

      touchBoundary: 这个参数是用于判断用户触摸屏幕后,移动的距离,如果大于10px,那么就不被看做是一次点击事件(具体实现后面介绍,下面的参数也同样会解释)。

      tapDelay: 这个参数规定了touchstart和touchend事件之间的200毫秒最小间隔,如果在这段时间内,发生了第二次点击将会被阻止。

      tapTimeout: 这个参数规定了一次tap事件(源码解释为tap事件)最长的事件,即touchstart和touchend事件之间的700毫秒最大间隔,超过这个时间,将不会被视作tap事件。

      当然还有很多参数,因为篇幅的关系,这里就不一一解释了,也不贴出源码,如果你想了解更多,请下载并阅读源码23~103行,每个参数都有详细的解释,只要学过高中英语都能读得懂- -(我四级没过都能读得懂。。。)

        主干部分解读(23~174)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    function FastClick(layer, options) {
            var oldOnClick;
     
            options = options || {};
     
            //这里本来是定义了一些参数的,但我在之前讲过了,这里的代码被我删掉了
     
            //如果是属于不需要处理的元素类型,则直接返回,notNeeded方法已在上方提到
            if (FastClick.notNeeded(layer)) {
                return;
            }
     
            //语法糖,兼容一些用不了 Function.prototype.bind 的旧安卓
            //所以后面不走 layer.addEventListener('click', this.onClick.bind(this), true);而是调用的这里的bind方法
            function bind(method, context) {
                return function() { return method.apply(context, arguments); };
            }
     
     
            var methods = ['onMouse''onClick''onTouchStart''onTouchMove''onTouchEnd''onTouchCancel'];
            var context = this;
            for (var i = 0, l = methods.length; i < l; i++) {
                context[methods[i]] = bind(context[methods[i]], context);//调用上面定义的bind()方法
            }
     
            //绑定事件,安卓需要做额外处理
            if (deviceIsAndroid) {
                layer.addEventListener('mouseover'this.onMouse, true);
                layer.addEventListener('mousedown'this.onMouse, true);
                layer.addEventListener('mouseup'this.onMouse, true);
            }
     
            layer.addEventListener('click'this.onClick, true);
            layer.addEventListener('touchstart'this.onTouchStart, false);
            layer.addEventListener('touchmove'this.onTouchMove, false);
            layer.addEventListener('touchend'this.onTouchEnd, false);
            layer.addEventListener('touchcancel'this.onTouchCancel, false);
     
            // 兼容不支持 stopImmediatePropagation 的浏览器
            if (!Event.prototype.stopImmediatePropagation) {
                layer.removeEventListener = function(type, callback, capture) {
                    var rmv = Node.prototype.removeEventListener;
                    if (type === 'click') {
                        rmv.call(layer, type, callback.hijacked || callback, capture);
                    else {
                        rmv.call(layer, type, callback, capture);
                    }
                };
     
                layer.addEventListener = function(type, callback, capture) {
                    var adv = Node.prototype.addEventListener;
                    if (type === 'click') {
                        //留意这里 callback.hijacked 中会判断 event.propagationStopped 是否为真来确保(安卓的onMouse事件)只执行一次
                        //在 onMouse 事件里会给 event.propagationStopped 赋值 true
                        adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
                                if (!event.propagationStopped) {
                                    callback(event);
                                }
                            }), capture);
                    else {
                        adv.call(layer, type, callback, capture);
                    }
                };
            }
     
            // 如果layer直接在DOM上写了 onclick 方法,那我们需要把它替换为 addEventListener 绑定形式
            if (typeof layer.onclick === 'function') {
                oldOnClick = layer.onclick;
                layer.addEventListener('click'function(event) {
                    oldOnClick(event);
                }, false);
                layer.onclick = null;
            }
        }

      在fastclick的主干部分,主要做了这么几件事情:
      1.定义一些参数,在后面的代码中会用到,作用已在前面提过。

      2.判断是否需要使用fastclick。

      3.绑定了事件:注意,这里绑定的都是fastclick中定义的事件,并不是原生事件,因为使用bind()方法做了处理,事件回调中的this都是fastclick实例上下文。

      4.兼容不支持 stopImmediatePropagation 的浏览器。

      5.将dom上写的onclick方法替换为addEventListener绑定形式

      核心部分解读(包括核心部分涉及到的方法)

      下面代码中的注释是我自己的理解,如有不对的地方请各位阅读者指出~~  

      1.onTouchStart(391-450): 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    FastClick.prototype.onTouchStart = function(event) {
            var targetElement, touch, selection;
     
            // 如果是多点触摸,将被忽略,直接返回true,不会执行后面代码
            if (event.targetTouches.length > 1) {
                return true;
            }
            //获得触摸对象,这个getTargetElementFromEventTarget方法将稍后讲解
            targetElement = this.getTargetElementFromEventTarget(event.target);
            touch = event.targetTouches[0];
                    //判断系统是否为ios
            if (deviceIsIOS) {
     
                // 在ios中,受信任的事件将会被取消,返回true。相关知识:如果一个事件是由设备本身(如浏览器)触发的,而不是通过JavaScript模拟合成的,那个这个事件被称为可信任的(trusted)
                //获得激活选中区
                selection = window.getSelection();
                //判断是否有range被选中&&选中“起点”和“结束点”是否重合,这一部分我猜测应该是ios自带的复制文字效果,为了防止用户意图复制文字时触发tap事件。
                if (selection.rangeCount && !selection.isCollapsed) {
                    return true;
                }
    //这一部分应该是对ios4中的bug进行处理吧,不过现在也没什么人用ios4这种古董系统,所以注释我就不翻译了,有兴趣自己去了解吧~
                if (!deviceIsIOS4) {
     
                    // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
                    // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
                    // with the same identifier as the touch event that previously triggered the click that triggered the alert.
                    // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
                    // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
                    // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
                    // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
                    // random integers, it's safe to to continue if the identifier is 0 here.
                    if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {
                        event.preventDefault();
                        return false;
                    }
     
                    this.lastTouchIdentifier = touch.identifier;
     
                    // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
                    // 1) the user does a fling scroll on the scrollable layer
                    // 2) the user stops the fling scroll with another tap
                    // then the event.target of the last 'touchend' event will be the element that was under the user's finger
                    // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
                    // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
                    this.updateScrollParent(targetElement);
                }
            }
     
    //记录click已经发生,这也是一个参数哟!
            this.trackingClick = true;
    //记录click发生的时间戳,参数一员
            this.trackingClickStart = event.timeStamp;
    //记录click的目标对象,参数一员
            this.targetElement = targetElement;
     
    //这里不解释,你们懂得
            this.touchStartX = touch.pageX;
            this.touchStartY = touch.pageY;
     
            //防止200ms内的多次点击,tapDelay这个参数在上面提到过
            if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
                event.preventDefault();
            }
     
            return true;
        };

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
     
            // 在一些旧的浏览器(尤其是Safari浏览器在iOS4.1)事件目标可能是一个文本节点。那么这个时候返回它的父节点。(⊙o⊙)…涨知识,不过目前来看可能这种情况很少了。
            if (eventTarget.nodeType === Node.TEXT_NODE) {
                return eventTarget.parentNode;
            }
     
            return eventTarget;
        };

     onTouchStart这个单词,很容易让我们知道fastclick中的tap仍然是通过touch事件进行模拟的,在touchStart时,fastclick主要做了这么几件事:

      1.忽略了多点触摸的情况

      2.解决了一些兼容性问题(ios4 和 ios复制文字效果)

        3.追踪click事件,获得click对象,记录了发生click事件时的时间戳

      4.防止200ms内的多次点击

      这里其实有点乱,因为其实是touch事件,但是为什么记作click事件呢(有的时候又说是tap事件),我们可以这样理解:本质上发生是touch事件,而fastclick要根据touch事件模拟click(tap)事件,这有一些条件,当该次触摸事件符合条件时,便可以认为是一次click事件,tap事件就是相对于pc端的click事件,所以移动端tap事件==pc端click事件。恩,因为源码中用到了trackingClickStart和一些带click的参数,所以你们懂的。tap事件本身是不存在的,是一种合成事件。

      2.onTouchMove(476~488)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    FastClick.prototype.onTouchMove = function(event) {
            if (!this.trackingClick) {
                return true;
            }
     
            // 如果touchMove超过了规定距离(10px),那么取消追踪这次touch事件,不会被模拟为tap,可以理解为:用户手指在滑动屏幕。。
            if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
                this.trackingClick = false;
                this.targetElement = null;
            }
     
            return true;
        };

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    FastClick.prototype.touchHasMoved = function(event) {
            var touch = event.changedTouches[0], boundary = this.touchBoundary;
          //这里就是判断touchMove移动的距离(x轴和y轴)是否超过boundary(10px),超过返回true    
            if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
                return true;
            }
     
            return false;
        };

    onTouchMove很明显就是在触摸过程中手指发生位移触发的事件,fastclick在这里主要做了两件事:

      1.首先判断是否有符合条件的tranckingClick,tranck意思是追踪,就是在onTouchStart阶段提供的判断条件,条件通过那么该次touch事件将被追踪,记作tranckingClick。

      2.如果touchMove超过了规定距离(x轴或y轴10px),那么取消追踪这次touch事件,不会被模拟为tap,可以理解为:用户手指在滑动屏幕。。

      3.onTouchEnd(521~610)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    FastClick.prototype.onTouchEnd = function(event) {
            var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
                    //不多说了,你们懂的
            if (!this.trackingClick) {
                return true;
            }
     
            // 还是为了防止多次点击,不过这里多了一个参数cancleNextClick,该属性会在onMouse事件中被判断,为true则彻底禁用事件和冒泡
            if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
                this.cancelNextClick = true;
                return true;
            }
              //识别长按事件,tapTimeOut默认为700ms
            if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
                return true;
            }
     
            // 重置为false避免input事件意外取消
            this.cancelNextClick = false;
                    //标记touchEnd时间戳,方便下一次touchStart判定双击
            this.lastClickTime = event.timeStamp;
     
            trackingClickStart = this.trackingClickStart;          //重置这两个参数
            this.trackingClick = false;
            this.trackingClickStart = 0;
     
            //这里又修复了一个ios的bug,啪啪啪一大串英文实在读不懂,解决的是ios6的bug,没兴趣详细了解。。
            if (deviceIsIOSWithBadTarget) {
                touch = event.changedTouches[0];
     
                // In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
                targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
                targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
            }
     
            targetTagName = targetElement.tagName.toLowerCase();
            if (targetTagName === 'label') {//是lable的话激活其指向的组件
                            //findControl这个方法将在后面介绍,大概就是点击label的时候,找到他指向的元素,并获取焦点。
                forElement = this.findControl(targetElement);
                            //如果找到了对应的元素
                if (forElement) {
                    this.focus(targetElement);
                    if (deviceIsAndroid) {//安卓直接返回
                        return false;
                    }
     
                    targetElement = forElement;
                }
            else if (this.needsFocus(targetElement)) {//needsFocus方法我将稍后说明,用于判断目标元素是否需要获得焦点
                            //触摸在元素上的事件超过100ms,则置空targetElement并返回false,也就是去走原生的focus方法去了,至于为什么这么做,目前还不是太明白
                // 后面这里又解决了ios5、6上的两个兼容性bug,(⊙o⊙)…不多做研究了,因为这个情况已经太少了。
                if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
                    this.targetElement = null;
                    return false;
                }
                            //获得焦点(这里不是原生的)
                this.focus(targetElement);
                            //sendClick是重点,将在后面讲解,我们需要知道的是这里将立即触发,并没有300ms延迟
                this.sendClick(targetElement, event);
     
                // 这个地方是为了防止ios4、6、7上面select展开的问题
                if (!deviceIsIOS || targetTagName !== 'select') {
                    this.targetElement = null;
                    event.preventDefault();
                }
     
                return false;
            }
     
            if (deviceIsIOS && !deviceIsIOS4) {
     
                //又是ios的hack代码,貌似是解决滚动区域的点击问题
                scrollParent = targetElement.fastClickScrollParent;
                if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
                    return true;
                }
            }
     
            //确定目标元素是否需要原生click,方法后面会介绍
            if (!this.needsClick(targetElement)) {
                             //如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click
                event.preventDefault();
                            //触发一次模拟的click事件
                this.sendClick(targetElement, event);
            }
     
            return false;
        };                                 

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    FastClick.prototype.findControl = function(labelElement) {
     
        // 支持html5 control属性的话,返回其指向的元素
        if (labelElement.control !== undefined) {
            return labelElement.control;
        }
     
        // 支持html5 htmlFor属性的话,返回其指向的元素
        if (labelElement.htmlFor) {
            return document.getElementById(labelElement.htmlFor);
        }
     
        // 如果以上属性都不支持,尝试返回lable的后代元素
        return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //判断是否需要获得焦点
    FastClick.prototype.needsFocus = function(target) {
            switch (target.nodeName.toLowerCase()) {
            case 'textarea':
                return true;
            case 'select':
                return !deviceIsAndroid;
            case 'input':
                switch (target.type) {
                case 'button':
                case 'checkbox':
                case 'file':
                case 'image':
                case 'radio':
                case 'submit':
                    return false;
                }
     
                return !target.disabled && !target.readOnly;
            default://目标元素如果有'needsfocus'的类,那么返回true
                return (/needsfocus/).test(target.className);
            }
        };

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    //一看名字就知道是判断是否需要原生click事件
    FastClick.prototype.needsClick = function(target) {
            switch (target.nodeName.toLowerCase()) {
     
            // Don't send a synthetic click to disabled inputs (issue #62)
            case 'button':
            case 'select':
            case 'textarea':
                if (target.disabled) {
                    return true;
                }
     
                break;
            case 'input':
     
                //hack代码,ios6浏览器的bug,input[type='file']需要原生click事件
                if ((deviceIsIOS && target.type === 'file') || target.disabled) {
                    return true;
                }
     
                break;
            case 'label':
            case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames
            case 'video':
                return true;
            }
                    //这里需要注意了,后面会说明。
            return (/needsclick/).test(target.className);
        };       

      onTouchEnd这个方法的代码量比较多一些,因为解决了很多稀奇古怪的兼容性问题,写一个好的js插件还真是不容易,就解决个点击事件300ms延迟问题,hack代码我并没有非常认真的研究到底,也看的晕乎乎的。好了废话不多说,这一部分主要是做了这么几件事情:

      1.首先判断这次touch事件是否还是处于追踪状态,如果不是,那么什么都不做了。

      2.防止多次点击问题

      3.如果是长按事件不予理会  

        4.如果目标元素是lable,那么找到其指向的元素并获取焦点,如果不是,那么判断元素是否需要获取焦点,最后确认目标是否需要原生click事件,如果不需要那么屏蔽掉原生click事件,并触发一次模拟的click事件(tap事件)。

      5.解决了一大推兼容性问题。

         4.sendClick

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //合成一个click事件并在指定元素上触发
        FastClick.prototype.sendClick = function(targetElement, event) {
            var clickEvent, touch;
     
            // 在一些安卓机器中,得让页面所存在的 activeElement(聚焦的元素,比如input)失焦,否则合成的click事件将无效
            if (document.activeElement && document.activeElement !== targetElement) {
                document.activeElement.blur();
            }
     
            touch = event.changedTouches[0];
     
            // 合成(自定义事件) 一个 click 事件
            // 通过一个额外属性确保它能被追踪(tracked)
            clickEvent = document.createEvent('MouseEvents');
            clickEvent.initMouseEvent(this.determineEventType(targetElement), truetrue, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, falsefalsefalsefalse, 0, null);
            clickEvent.forwardedTouchEvent = true// fastclick的内部变量,用来识别click事件是原生还是合成的
            targetElement.dispatchEvent(clickEvent); //立即触发其click事件
        };
     
        FastClick.prototype.determineEventType = function(targetElement) {
     
            //安卓设备下 Select 无法通过合成的 click 事件被展开,得改为 mousedown
            if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
                return 'mousedown';
            }
     
            return 'click';
        };

      终于走到这一步,这里合成了一个click事件,并且合成的click事件立即触发,是没有300ms的延迟的~~~

       5.onMouse 和 onClick(630~704)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    //用于决定是否采用原生click事件
        FastClick.prototype.onMouse = function(event) {
     
            // touch事件一直没触发
            if (!this.targetElement) {
                return true;
            }
     
            if (event.forwardedTouchEvent) { //触发的click事件是合成的
                return true;
            }
            // 确保其没执行过 preventDefault 方法(event.cancelable 不为 true)即可
            if (!event.cancelable) {
                return true;
            }
     
            // 需要做预防穿透处理的元素,或者做了快速(200ms)双击的情况
            if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
                //停止当前默认事件和冒泡
                if (event.stopImmediatePropagation) {
                    event.stopImmediatePropagation();
                else {
     
                    // 不支持 stopImmediatePropagation 的设备做标记,
                    // 确保该事件回调不会执行
                    event.propagationStopped = true;
                }
     
                // 取消事件和冒泡
                event.stopPropagation();
                event.preventDefault();
     
                return false;
            }
     
             
            return true;
        };
     
     
        //click事件常规都是touch事件衍生来的,也排在touch后面触发。
        //对于那些我们在touch事件过程没有禁用掉默认事件的event来说,我们还需要在click的捕获阶段进一步
        //做判断决定是否要禁掉点击事件
        FastClick.prototype.onClick = function(event) {
            var permitted;
     
            // 如果还有 trackingClick 存在,可能是某些UI事件阻塞了touchEnd 的执行
            if (this.trackingClick) {
                this.targetElement = null;
                this.trackingClick = false;
                return true;
            }
     
            // 依旧是对 iOS 怪异行为的处理 —— 如果用户点击了iOS模拟器里某个表单中的一个submit元素
            // 或者点击了弹出来的键盘里的“Go”按钮,会触发一个“伪”click事件(target是一个submit-type的input元素)
            if (event.target.type === 'submit' && event.detail === 0) {
                return true;
            }
     
            permitted = this.onMouse(event);
     
            if (!permitted) { //如果点击是被允许的,将this.targetElement置空可以确保onMouse事件里不会阻止默认事件
                this.targetElement = null;
            }
     
            //没有什么意义返回这个
            return permitted;
        };

      常规需要阻断点击事件的操作,在touch 监听事件回调中已经做了处理,这里主要是针对那些 touch 过程(有些设备甚至可能并没有touch事件触发)没有禁用默认事件的 event 做进一步处理,从而决定是否触发原生的 click 事件(如果禁止是在 onMouse 方法里做的处理)。

       总结

      新知识get:

        stopImmediatePropagation与stopPropagation区别:

      1. 他们都可以阻止事件冒泡到父元素

      2. stopImmediatePropagation多做了一件事:比如某个元素绑定多个相同类型事件监听函数,如果执行了stopImmediatePropagation,将按照顺序执行第一个事件监听函数,其余相同类型事件监听函数被阻止。

        zepto“点透”现象被解决是为什么?

        这一点因为我还没有去阅读zepto的源码,所以暂时不能解答。。等待之后再去挖掘。      

     

      第一次阅读源码,感觉很困难,很多东西都不知道,去github上面找问题,但英语太渣,有些看不懂,连蒙带猜加翻译,最终还是求助于百度和谷歌,看到了很多大神的对fastclick的分析文章,感觉自己还有很远的路要走~  

    https://blog.csdn.net/handsomexiaominge/article/details/80545902   地址

    玩过移动端web开发的同学应该都了解过,移动端上的click事件都会有300毫秒的延迟,这300毫秒主要是浏览器为了判断你当前的点击时单击还是双击,但有时候为了更快的对用户的操作做出更快的响应,越过这个300毫秒的延迟是有点必要的,FastClick做的就是这件事,这篇文章会理清FastClick的整体思路,分析主要的代码,但不会贴出所有的代码,仅分析主干,由于历史原因,FastClick对旧版本的机型做了很多兼容性适配,例如ios4,这部分代码到现在显然已经没有什么分析的意义了,所以贴出的代码会将这部分代码删除。

    首先,我们分析一下总体的实现思路,其实FastClick做的事情很简单,首先判断当前浏览器需不需要使用FastClick,例如桌面浏览器,那就不需要,直接绕过,接着,如果需要,则在click事件中拦截事件,取消所有绑定事件的操作,接着用一系列touch事件(touchstart,touchmove,touchend)来模拟click事件,由于touch事件不会延迟,从而达到绕过300毫秒延迟的效果。

    先看看FastClick是如何判断浏览器是否需要FastClick的

    1.  
      FastClick.notNeeded = function(layer) {
    2.  
      var metaViewport;
    3.  
      var chromeVersion;
    4.  
      var blackberryVersion;
    5.  
      var firefoxVersion;
    6.  
       
    7.  
      // Devices that don't support touch don't need FastClick
    8.  
      //不支持用于模拟的touchstart事件,无法模拟
    9.  
      if (typeof window.ontouchstart === 'undefined') {
    10.  
      return true;
    11.  
      }
    12.  
       
    13.  
      // 探测chome浏览器
    14.  
      chromeVersion = +(/Chrome/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
    15.  
       
    16.  
      if (chromeVersion) {
    17.  
       
    18.  
      //安卓设备
    19.  
      if (deviceIsAndroid) {
    20.  
      metaViewport = document.querySelector('meta[name=viewport]');
    21.  
       
    22.  
      if (metaViewport) {
    23.  
      // 安卓下,带有 user-scalable="no" 的 meta 标签的 chrome 是会自动禁用 300ms 延迟的,无需 FastClick
    24.  
      if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
    25.  
      return true;
    26.  
      }
    27.  
      //chome32以上带有 width=device-width的meta标签的也唔需要使用FastClick
    28.  
      if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
    29.  
      return true;
    30.  
      }
    31.  
      }
    32.  
       
    33.  
      // 桌面设备自然无需使用
    34.  
      } else {
    35.  
      return true;
    36.  
      }
    37.  
      }
    38.  
       
    39.  
      //黑莓浏览器,这个。。。了解就好
    40.  
      if (deviceIsBlackBerry10) {
    41.  
      //检测黑莓浏览器
    42.  
      blackberryVersion = navigator.userAgent.match(/Version/([0-9]*).([0-9]*)/);
    43.  
       
    44.  
      // 黑莓10.3以上部分可以不适用FastClick
    45.  
      if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
    46.  
      metaViewport = document.querySelector('meta[name=viewport]');
    47.  
       
    48.  
      if (metaViewport) {
    49.  
      // 跟chome一样
    50.  
      if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
    51.  
      return true;
    52.  
      }
    53.  
      // 跟chome一样
    54.  
      if (document.documentElement.scrollWidth <= window.outerWidth) {
    55.  
      return true;
    56.  
      }
    57.  
      }
    58.  
      }
    59.  
      }
    60.  
       
    61.  
      //ie10带有msTouchAction,touchAction相关样式的不需要FastClick
    62.  
      if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {
    63.  
      return true;
    64.  
      }
    65.  
       
    66.  
      //firefox,跟chome差不多
    67.  
      firefoxVersion = +(/Firefox/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
    68.  
       
    69.  
      if (firefoxVersion >= 27) {
    70.  
      // Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896
    71.  
       
    72.  
      metaViewport = document.querySelector('meta[name=viewport]');
    73.  
      if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {
    74.  
      return true;
    75.  
      }
    76.  
      }
    77.  
       
    78.  
      //ie11检测,跟ie10一样,只是ie11废弃了msTouchAction,改为touchAction,依旧是检测样式,检测到相关样式不用FastClick
    79.  
      if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
    80.  
      return true;
    81.  
      }
    82.  
       
    83.  
      //黑名单之外放行,都使用FastClick
    84.  
      return false;
    85.  
      };

    长长的一大段,基本上采用黑名单策略,分别检测了chome,黑莓,firefox,ie10,ie11,基本上都是检测对应的meta标签,检测到对应的值的话,弃用FastClick,黑名单之外启用FastClick,仅仅是一个检测函数,看看就好,没什么研究的价值

    主体流程,看看FastClick的构造函数,此处仅贴出主要代码,删除了一些兼容的代码

    1.  
      function FastClick(layer, options) {
    2.  
       
    3.  
      //不需要fastClick时直接返回
    4.  
      if (FastClick.notNeeded(layer)) {
    5.  
      return;
    6.  
      }
    7.  
       
    8.  
      //简单的兼容bind方法
    9.  
      function bind(method, context) {
    10.  
      return function() { return method.apply(context, arguments); };
    11.  
      }
    12.  
       
    13.  
       
    14.  
      //注册内部事件
    15.  
      var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
    16.  
      var context = this;
    17.  
      for (var i = 0, l = methods.length; i < l; i++) {
    18.  
      context[methods[i]] = bind(context[methods[i]], context);
    19.  
      }
    20.  
                      //捕获阶段做拦截事件处理
    21.  
      layer.addEventListener('click', this.onClick, true);
    22.  
      layer.addEventListener('touchstart', this.onTouchStart, false);
    23.  
      layer.addEventListener('touchmove', this.onTouchMove, false);
    24.  
      layer.addEventListener('touchend', this.onTouchEnd, false);
    25.  
      layer.addEventListener('touchcancel', this.onTouchCancel, false);
    26.  
       
    27.  
       
    28.  
      //处理通过标签属性绑定事件的方式,转化为通过addEventListener绑定事件,确保fastclick的各种兼容能顺利执行
    29.  
      if (typeof layer.onclick === 'function') {
    30.  
      oldOnClick = layer.onclick;
    31.  
      layer.addEventListener('click', function(event) {
    32.  
      oldOnClick(event);
    33.  
      }, false);
    34.  
      layer.onclick = null;
    35.  
      }
    36.  
      }

    FastClick会在执行FastClick.attach操作时被实例化,从代码我们可以看到,做了几件事,检测是否需要使用FastClick,之后注册了一些列的内部方法(onmouse,onclik,ontouchstart等等)并绑定当前作用域,捕获阶段处理onclick事件,冒泡阶段处理touch相关事件并定义相关的内部处理函数,最后对于用标签绑定事件的方式修改为用addEventListener的方式绑定。至于为什么为什么要在捕获阶段处理onclick,我们都知道,现代浏览器对于事件的处理都是先发生捕获,之后再发生冒泡,而为了兼容旧版本浏览器,默认的做法都是将事件绑定在冒泡阶段,在冒泡阶段处理click事件,我们就可以拦截到click事件,并把后续的click绑定操作全都取消掉。

    所以,我们大概可以看到,FastClick里面最主要的几个主要方法:onMouse,onClick,onTouchStart,onTouchMoce,onTouchEnd,onTouchMove,onTouchCancel,接下来我们将会逐个分析这些方法

    首先,onClick方法

    1.  
      FastClick.prototype.onClick = function(event) {
    2.  
      var permitted;
    3.  
       
    4.  
      // 标记未被取消,直接取消
    5.  
      if (this.trackingClick) {
    6.  
      this.targetElement = null;
    7.  
      this.trackingClick = false;
    8.  
      return true;
    9.  
      }
    10.  
       
    11.  
      //submit控件不做处理
    12.  
      if (event.target.type === 'submit' && event.detail === 0) {
    13.  
      return true;
    14.  
      }
    15.  
       
    16.  
      permitted = this.onMouse(event);
    17.  
       
    18.  
      if (!permitted) {
    19.  
      this.targetElement = null;
    20.  
      }
    21.  
       
    22.  
      return permitted;
    23.  
      };

    此处有必要解释一下trackingClick和targetElement这两个标记,trackingClick是一个追踪标志,用touch事件模拟时,正常情况下,开始时(touchstart)会被设置为true,模拟结束(touchend)会被设置为false,而click事件会在touchend事件中被模拟发出,这个后面分析代码的时候我们会看到,很明显,这个时候trackingClick如果检测到为true,是一种不正常的现象,这里FastClick的作者解释为you可能使用了类似的第三方库,导致click事件比FastClick更快的发出,所以此处就不再对结果进行处理,并将内部变量重现修改为默认状态。接着,我们看到,onclick方法其实在内部调用了onmouse方法,事实上主要的操作也都是在onmouse里面执行的,接下来我们看看onMouse

    1.  
      FastClick.prototype.onMouse = function(event) {
    2.  
       
    3.  
      //当前target缺失,有可能模拟触发已经被取消,没有必要阻止 ,直接触发原生事件
    4.  
      if (!this.targetElement) {
    5.  
      return true;
    6.  
      }
    7.  
              //模拟事件标识符
    8.  
      if (event.forwardedTouchEvent) {
    9.  
      return true;
    10.  
      }
    11.  
       
    12.  
      // 事件无法阻止
    13.  
      if (!event.cancelable) {
    14.  
      return true;
    15.  
      }
    16.  
       
    17.  
      //需要fastclick是阻止所有事件触发,快速点击时亦如此
    18.  
      if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
    19.  
       
    20.  
      // Prevent any user-added listeners declared on FastClick element from being fired.
    21.  
      //解除所有后续事件的触发,包括当前节点绑定的其他事件
    22.  
      if (event.stopImmediatePropagation) {
    23.  
      event.stopImmediatePropagation();
    24.  
      } else {
    25.  
       
    26.  
      // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
    27.  
      event.propagationStopped = true;
    28.  
      }
    29.  
       
    30.  
      // 阻止冒泡,阻止默认操作
    31.  
      event.stopPropagation();
    32.  
      event.preventDefault();
    33.  
      return false;
    34.  
      }
    35.  
       
    36.  
      // If the mouse event is permitted, return true for the action to go through.
    37.  
      return true;
    38.  
      };

    首先,进入onMouse之后,会通过函数needClick判断当前点击的控件是否需要原生点击的支持,避免出现一些bug,然后判断this.cancelNextClick是否为true,cancelNextClick是用于判断当前操作是否要取消的一个标识符,当两次点击的间隔小于配置的值时,cancelNextClick会被设置为true,这个操作在touchend中进行,稍后会进行分析。当条件满足时,执行阻止事件的操作,具体是执行event.stopImmediatePropagation方法,他能阻止此操作之后绑定在这个节点上的所有其他操作,对于不支持的浏览器,会在event中添加一个propagationStopped的属性,用于兼容操作,这个兼容操作后面再说,接着就是各种阻止冒泡,阻止默认操作,至此,整个阻止操作就完成了,接下来就是如何不延迟300毫秒来触发click事件了,上面说了,用touch事件进行模拟,具体如何,往下走

    首先,onTouchStart

    1.  
      FastClick.prototype.onTouchStart = function(event) {
    2.  
      var targetElement, touch, selection;
    3.  
       
    4.  
      //忽略多点触控
    5.  
      if (event.targetTouches.length > 1) {
    6.  
      return true;
    7.  
      }
    8.  
       
    9.  
      targetElement = this.getTargetElementFromEventTarget(event.target);
    10.  
      touch = event.targetTouches[0];
    11.  
       
    12.  
      //记录跟踪状态
    13.  
      this.trackingClick = true;
    14.  
      //记录开始点击时间
    15.  
      this.trackingClickStart = event.timeStamp;
    16.  
      //记录当前处理的节点
    17.  
      this.targetElement = targetElement;
    18.  
       
    19.  
      //记录当前位置
    20.  
      this.touchStartX = touch.pageX;
    21.  
      this.touchStartY = touch.pageY;
    22.  
       
    23.  
      // Prevent phantom clicks on fast double-tap (issue #36)
    24.  
      //阻止双击事件的默认动作
    25.  
      if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
    26.  
      event.preventDefault();
    27.  
      }
    28.  
       
    29.  
      return true;
    30.  
      };

    onTouchStart做的事情其实比较少,上面的代码去掉了一些兼容性操作,剩下的只是记录一些基础性的信息,唯一做的事情就是阻止了双击事件的默认操作,如何判断是双击的,event.timeStamp记录了当前点击的时间戳,this.lastClickTime为上一次onTouchEnd时记录的值,记录最后一次点击完成的时间,两者相减小于配置值,则认为是双击,FastClick默认配置的this.tapDelay为200毫秒

    接着是onTouchMove

    1.  
      FastClick.prototype.onTouchMove = function(event) {
    2.  
      //没有触发过touchstart事件,直接返回
    3.  
      if (!this.trackingClick) {
    4.  
      return true;
    5.  
      }
    6.  
       
    7.  
      // If the touch has moved, cancel the click tracking
    8.  
      //判断当前是否移动,移动过则取消跟踪事件
    9.  
      if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
    10.  
      this.trackingClick = false;
    11.  
      this.targetElement = null;
    12.  
      }
    13.  
       
    14.  
      return true;
    15.  
      };

    操作也是比较简单,trackingClick是一个跟踪字段,在onTouchStart中设置为true,如此处发现不为true,则发生了错误,直接会返回,接着就是判断当前是否有移动,主要就是获取当前手指的位置跟触发控件的位置进行比较,具体方法由于篇幅关系就不解释了,本篇博文仅解释主干内容,当触摸点移动了,则将trackingClcik和targetElement恢复为默认,之后在touchEnd中就不会发出模拟事件触发click

    接着对于特殊原因取消的情况,绑定了touchcancel事件

    1.  
      FastClick.prototype.onTouchCancel = function() {
    2.  
      this.trackingClick = false;
    3.  
      this.targetElement = null;
    4.  
      };

    这个并没有什么特别的地方,特殊情况发生了,如手指戳下的时候突然来电话了各种情况导致触摸中断,则将所有跟踪变量恢复到初始状态。

    最关键的onTouchEnd

    1.  
      FastClick.prototype.onTouchEnd = function(event) {
    2.  
      var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
    3.  
                      //触摸点移动或者其他操作导致取消
    4.  
      if (!this.trackingClick) {
    5.  
      return true;
    6.  
      }
    7.  
       
    8.  
      //不处理快速点击
    9.  
      if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
    10.  
      this.cancelNextClick = true;
    11.  
      return true;
    12.  
      }
    13.  
       
    14.  
      //不处理长按
    15.  
      if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
    16.  
      return true;
    17.  
      }
    18.  
       
    19.  
      // 将所有的跟踪变量设置为初始状态,供下次点击使用
    20.  
      this.cancelNextClick = false;
    21.  
       
    22.  
      this.lastClickTime = event.timeStamp;
    23.  
       
    24.  
      trackingClickStart = this.trackingClickStart;
    25.  
      this.trackingClick = false;
    26.  
      this.trackingClickStart = 0;
    27.  
       
    28.  
      targetTagName = targetElement.tagName.toLowerCase();
    29.  
      //处理组件为label时的状况,获取label对应绑定的控件
    30.  
      if (targetTagName === 'label') {
    31.  
      forElement = this.findControl(targetElement);
    32.  
      if (forElement) {
    33.  
      this.focus(targetElement);
    34.  
      if (deviceIsAndroid) {
    35.  
      return false;
    36.  
      }
    37.  
       
    38.  
      targetElement = forElement;
    39.  
      }
    40.  
      } else if (this.needsFocus(targetElement)) {
    41.  
       
    42.  
      //第一个判断作者认为如果按下的时间超过了100毫秒,此时已经没有必要再执行模拟操作了,按原生的click执行操作即可,第二个判断则是处理ios相关的一个bug
    43.  
      if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
    44.  
      this.targetElement = null;
    45.  
      return false;
    46.  
      }
    47.  
       
    48.  
      this.focus(targetElement);
    49.  
      this.sendClick(targetElement, event);
    50.  
       
    51.  
      return false;
    52.  
      }
    53.  
       
    54.  
      //不需要原生点击时,触发模拟click事件
    55.  
      if (!this.needsClick(targetElement)) {
    56.  
      event.preventDefault();
    57.  
      this.sendClick(targetElement, event);
    58.  
      }
    59.  
       
    60.  
      return false;
    61.  
      };

    此处,ontouchEnd,首先忽略快速点击和长按,然后恢复所有的初始化变量,之后会判断当前控件是不是label,是的话利用findControl函数找到label关联的组件,并赋值给当前的targetElement 统一处理,具体杂七杂八的函数会在后面再解释,接着会判断当前组件触发click时需不需要获取焦点,如果需要,则获取焦点后,触发模拟事件,此处关注两个函数focus和sendClick,focus函数帮助当前target获取焦点,sendClick则发送模拟事件,focus函数关键代码如下

    1.  
      /**
    2.  
      * 兼容写法,获取焦点,光标放置到末尾
    3.  
      */
    4.  
      FastClick.prototype.focus = function(targetElement) {
    5.  
      var length;
    6.  
      if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {
    7.  
      length = targetElement.value.length;
    8.  
      targetElement.setSelectionRange(length, length);
    9.  
      } else {
    10.  
      targetElement.focus();
    11.  
      }
    12.  
      };

    此处,对于ios浏览器,采用兼容的写法,用setSelectionRange来获取焦点,setSelectionRange可以用来选取输入框的值,此处将选取的开始和结束都设置为value的length,则可以把光标放到组件的末尾并且获得焦点

    接下来是sendClick,这也是整个fastclick的关键,用于模拟事件的发生,主要实现如下:

    1.  
      FastClick.prototype.sendClick = function(targetElement, event) {
    2.  
      var clickEvent, touch;
    3.  
       
    4.  
      //兼容操作,部分安卓机当前焦点所在的节点如果不是模拟节点,需要把焦点去除,否则影响效果
    5.  
      if (document.activeElement && document.activeElement !== targetElement) {
    6.  
      document.activeElement.blur();
    7.  
      }
    8.  
       
    9.  
      touch = event.changedTouches[0];
    10.  
       
    11.  
      // Synthesise a click event, with an extra attribute so it can be tracked
    12.  
      clickEvent = document.createEvent('MouseEvents');
    13.  
      clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
    14.  
      clickEvent.forwardedTouchEvent = true;
    15.  
      targetElement.dispatchEvent(clickEvent);
    16.  
      };
    实现代码很简单,就是就是创建一个event对象,然后触发它,注意,这个地方用到了initMouseEvent来初始化event对象,但目前initMouseEvent已经从web删除了,换句话说它已经不是标准方法了,未来的浏览器可能不会再继续提供支持,所以自己尽量不要使用这个特性,可以用MouseEvent这个特定的事件构造器来替代它,详细使用方法可以参考戳我带你飞
     

    至此,我们的所有主流程已经讲完了,接下来我们说一下里面涉及到的一些杂七杂八的函数

    首先,如何兼容event.stopImmediatePropagation,上面我们说了,这个函数可以解除当前绑定操作之后的所有绑定到此节点上的操作,但存在部分浏览器不兼容,对于一些不兼容的浏览器,上面说到绑定事件fastclick会手动给event对象添加一个propagationStopped属性,那这个属性有什么用呢,我们看看下面的代码

    1.  
      layer.addEventListener = function(type, callback, capture) {
    2.  
      var adv = Node.prototype.addEventListener;
    3.  
      if (type === 'click') {
    4.  
      adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
    5.  
      //通过对event对象添加属性来控制事件的触发
    6.  
      if (!event.propagationStopped) {
    7.  
      callback(event);
    8.  
      }
    9.  
      }), capture);
    10.  
      } else {
    11.  
      adv.call(layer, type, callback, capture);
    12.  
      }
    13.  
      };

    这段函数出现在fastclick的构造函数中,为了主干代码的清晰,在上面我把它删掉了,对于不兼容event.stopImmediatePropagation的浏览器,它重写了addEventListener方法,增加了对stopImmediatePropagation属性的判断,这样当上面的propagationStopped被设置为true的时候,后续的绑定操作就都不会继续进行了。

    接下来一个方法是获取label关联控件的方法,findControl

    1.  
      FastClick.prototype.findControl = function(labelElement) {
    2.  
       
    3.  
      //通过control属性获取
    4.  
      if (labelElement.control !== undefined) {
    5.  
      return labelElement.control;
    6.  
      }
    7.  
       
    8.  
      //通过获取for属性
    9.  
      if (labelElement.htmlFor) {
    10.  
      return document.getElementById(labelElement.htmlFor);
    11.  
      }
    12.  
       
    13.  
      //如各种不兼容,则获取label标签中的第一个
    14.  
      return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
    15.  
      };

    首先,findControl会通过html5的control属性来获取label包含的表单元素,如果失败,转而获取label的for属性对应的表单元素,因为for属性也是html5的,旧浏览器可能不兼容,最后如果获取不了,则会获取label元素的子元素中的第一个表单元素,进而来获取label对应的表单元素。

    嗯,啰啰嗦嗦大概说完了,如有说错的地方,欢迎评论区指出

  • 相关阅读:
    csharp上传文件到服务器指定文件夹问题
    c#上传文件到服务器指定文件夹问题
    jsp上传文件到服务器指定文件夹问题
    java上传文件到服务器指定文件夹问题
    asp.net上传文件到服务器指定文件夹问题
    .net批量下载图片
    连续肝了好几天,终于把Java面试必备最重要的基础知识【Java集合】知识点总结整理出来了。
    来晚了,秋招五投大厂,成功拿下三家Offer,最终入职美团,分享我的美团1-4面(Java岗)
    变秃了,也变强了!爆肝吐血整理出的超硬核JVM笔记分享!
    Spring框架及七大模块
  • 原文地址:https://www.cnblogs.com/chaoyuehedy/p/9244600.html
Copyright © 2011-2022 走看看