深入解析FastClick解决延迟点击

下载

源码和文档gitHub上都有,有兴趣的可以去逛逛 https://github.com/ftlabs/fastclick

使用

<script src="./js/fastclick.js"></script>
<script>
    window.addEventListener( "load", function() {
        FastClick.attach( document.body );
    }, false );
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

源码解析

attach方法:

FastClick.attach = function(layer) {
    'use strict';
    return new FastClick(layer);
};
  • 1
  • 2
  • 3
  • 4

在FastClick的构造函数中

this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };

    this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };

    this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };

    this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };

    this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };

    if (FastClick.notNeeded(layer)) {
        return;
    }
    if (this.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('touchend', this.onTouchEnd, false);
    layer.addEventListener('touchcancel', this.onTouchCancel, false);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

也就是在document.body上绑定了click,touchstart,touchend,touchcancel事件。这里假设,我们的页面有一个button,绑定了click事件。当用户点击此button时,会先触发touchstart事件,这时,会冒泡到document.body中,于是就会执行:

FastClick.prototype.onTouchStart = function(event) {
    'use strict';
    var targetElement, touch, selection;

    if (event.targetTouches.length > 1) {
        return true;
    }
    targetElement = this.getTargetElementFromEventTarget(event.target);
    touch = event.targetTouches[0];

    if (this.deviceIsIOS) {

        selection = window.getSelection();
        if (selection.rangeCount && !selection.isCollapsed) {
            return true;
        }

        if (!this.deviceIsIOS4) {

            if (touch.identifier === this.lastTouchIdentifier) {
                event.preventDefault();
                return false;
            }

            this.lastTouchIdentifier = touch.identifier;    
            this.updateScrollParent(targetElement);
        }
    }
    this.trackingClick = true;
    this.trackingClickStart = event.timeStamp;
    this.targetElement = targetElement;
    this.touchStartX = touch.pageX;
    this.touchStartY = touch.pageY;
    if ((event.timeStamp - this.lastClickTime) < 200) {
        event.preventDefault();
    }
    return true;
};
  • 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

这个回调函数主要做了以下事情:

  1. 获取我们当前触发touchstart的元素,这里是button。
  2. 然后将鼠标的信息记录了下来,记录鼠标的信息主要是为了在后面touchend触发时,根据这里得到的x、y判断是否为click。
  3. 触发touchend事件,然后冒泡到document.body上,执行以下代码:
FastClick.prototype.onTouchEnd = function(event) {
    'use strict';
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
    if (this.touchHasMoved(event) || (event.timeStamp - this.trackingClickStart) > 300) {
        this.trackingClick = false;
        this.targetElement = null;
    }
    if (!this.trackingClick) {
        return true;
    }
    if ((event.timeStamp - this.lastClickTime) < 200) {
        this.cancelNextClick = true;
        return true;
    }

    this.lastClickTime = event.timeStamp;
    trackingClickStart = this.trackingClickStart;
    this.trackingClick = false;
    this.trackingClickStart = 0;
    if (this.deviceIsIOSWithBadTarget) {
        touch = event.changedTouches[0];
        targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset);
    }
    targetTagName = targetElement.tagName.toLowerCase();
    if (targetTagName === ‘label‘) {
        forElement = this.findControl(targetElement);
        if (forElement) {
            this.focus(targetElement);
            if (this.deviceIsAndroid) {
                return false;
            }

            targetElement = forElement;
        }
    } else if (this.needsFocus(targetElement)) {
        if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === ‘input‘)) {
            this.targetElement = null;
            return false;
        }

        this.focus(targetElement);
        if (!this.deviceIsIOS4 || targetTagName !== 'select') {
            this.targetElement = null;
            event.preventDefault();
        }
        return false;
    }
    if (this.deviceIsIOS && !this.deviceIsIOS4) {
        scrollParent = targetElement.fastClickScrollParent;
        if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
            return true;
        }
    }
    if (!this.needsClick(targetElement)) {
        event.preventDefault();
        this.sendClick(targetElement, event);
    }
    return false;
};
  • 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

注意上面的代码中,event.preventDefault();会阻止真实的click事件的触发,因此,在button上面的click事件不会触发。接下来,我们只需要查看sendClick方法。

FastClick.prototype.sendClick = function(targetElement, event) {
    'use strict';
    var clickEvent, touch;
    if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
    }
    touch = event.changedTouches[0];

    clickEvent = document.createEvent(‘MouseEvents‘);
      
    clickEvent.initMouseEvent(‘click‘, true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在此方法中,会创建一个自定义的click事件,然后在button上立即触发,于是,button绑定的click的事件回调函数马上执行,因此就没有300ms延迟了。

上面的initMouseEvent方法的前三个参数的意思:1.事件类型,2.是否冒泡,3.是否阻止浏览器的默认行为。

自定义的click事件阻止了浏览器的默认行为,事件冒泡,于是执行document.body的click事件回调函数。代码如下:

FastClick.prototype.onClick = function(event) {
    'use strict';
    var permitted;
    if (this.trackingClick) {
        this.targetElement = null;
        this.trackingClick = false;
        return true;
    }
    if (event.target.type === ‘submit‘ && event.detail === 0) {
        return true;
    }
    permitted = this.onMouse(event);
    if (!permitted) {
        this.targetElement = null;
    }
    return permitted;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

然后里面有一句 permitted = this.onMouse(event);于是,我们查看onMouse方法:

FastClick.prototype.onMouse = function(event) {
    'use strict';
    if (!this.targetElement) {
        return true;
    }
    if (event.forwardedTouchEvent) {
        return true;
    }
    if (!event.cancelable) {
        return true;
    }
    if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
        if (event.stopImmediatePropagation) {
            event.stopImmediatePropagation();
        } else {
            event.propagationStopped = true;
        }
        event.stopPropagation();
        event.preventDefault();
        return false;
    }
    return true;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这个方法会阻止模拟的click事件的冒泡以及默认行为,于是就解决了移动端浏览器click事件延迟300ms的问题。代码比较长,大家能看懂7788就差不多了 ^_^

扫描二维码关注公众号,回复: 3182747 查看本文章

不足

唯一的缺点可能也就是该脚本的文件尺寸 (尽管它只有 10kb)。如果你非常在意这点文件大小,可以尝试一下 Filament Group 的 Tappy!,或者 tap.js。两者都相当轻量,能够通过监听tap而非click事件来绕过 300 毫秒延迟。

猜你喜欢

转载自blog.csdn.net/gwdgwd123/article/details/82383814
今日推荐