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

    fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。

    基本结构

    function FastClick(layer, options) {}
    // ......
    FastClick.attach = function(layer, options) {
        return new FastClick(layer, options);
    };
    // 这个方法用于判断是否需要fastclick,因为如果用户设置了如禁止播放的功能,
    // 那么默认将不需要fastClick, 这个方法代码长,这里不贴,里面是关于不同版本
    // 浏览器判断和对user-scalable的获取判断
    FastClick.notNeeded = function(layer) { // ... }

    可以看到基本结构很简单,定义一个构造函数,并且挂载一个静态函数attach(js没有面向对象语法,但是如果在构造函数挂载函数,实际上等同于面向对象的Class.staticMethod,即不需要实例即可访问的函数,同理,原型上的方法是需要创建实例才能使用)。
    attach方法就是创建一个FastClick实例,所以基本上,fastClick的操作就在构造函数初始化内完成。接下来继续看里面的内容。

    对象结构

    attach方法已经拜读,可以先忽略了。

    // 对象属性
    function FastClick(layer, options) {
        this.trackingClick = false;     // 当touchStart时候置为true
        this.trackingClickStart = 0;   // 当touchStart时候的时间错 
        this.targetElement = null;     // 触发的元素
        this.touchStartX = 0;            // touch位置x轴
        this.touchStartY = 0;            // touch位置y轴
        this.lastTouchIdentifier = 0; // 注释:最后触摸的id,取自Touch.identifier.
        this.touchBoundary = options.touchBoundary || 10; // move边界
        this.layer = layer;                  // 挂载的dom元素,该dom元素下fastclick将生效
        this.tapDelay = options.tapDelay || 200;
        this.tapTimeout = options.tapTimeout || 700;
        // ... 执行过程
    }

    构造函数执行过程

    function FastClick(layer, options) {
      // ...属性已省略
      var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];  // 列举需要挂载实例中回到到layer上的事件
      var context = this;
      for (var i = 0, l = methods.length; i < l; i++) {
        context[methods[i]] = bind(context[methods[i]], context); // 绑定fastclick对象的上下文给回调函数, 这些回调是定义在构造函数的prototype上,后面会看到
      }
      // Set up event handlers as required 按要求挂载处理事件
      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);
      // ...	
    }

    构造函数核心过程如图,它会给layer挂载需要处理的回调事件,layer作为上层节点,能够获取到target节点所发生的事件,至于在各种事件中如何处理这个问题,我们会在后续的代码中看到。

    bind方法的实现如下, 简单看下,没啥说的,比较简单:

    // Some old versions of Android don't have Function.prototype.bind
    function bind(method, context) {
        return function() { return method.apply(context, arguments); };
    }

    设计思路

    关于设计思路,就是说如果是我们模拟这样一个事件,我们需要怎么去判断为点击事件,我想大概可以从几个点去考虑。

    1. 一个点击事件,它必须包含一个点,一个起的动作(start, end)。
    2. 一个点击事件,它必须是迅速的,也就是说,如果你一直按着不动,那么这不是一个点击事件,而是一个长按事件,所以我们还需要一个时间区间,对于在这个时间区间范围内我们才认为是点击事件。所以我们看fastclick的源码属性中,tapDelay就是这样的作用,默认是200ms,可以人为配置。
    3. 一个点击事件,它必须是短距的,也就是说,如果你移动了一个比较长的距离,那么我们认定为是一个移动事件,这也不算是点击事件,可以在touchMove里面去判断。

    Fastclick的原型方法

    原型方法即实例方法

    FastClick.prototype.focus = function(targetEle) { // ... };
    FastClick.prototype.updateScrollParent = function(targetEle) { // .. };
    FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { // ... };
    FastClick.prototype.touchHasMoved = function(event) { // ... };
    FastClick.prototype.findControl = function(event) { // ... };
    FastClick.prototype.onTouch = function(event) { // ... };
    FastClick.prototype.destroy = function(event) { // ... };
    
    FastClick.prototype.onTouchStart = function(event) { // ... };
    FastClick.prototype.onTouchMove = function(event) { // ... };
    FastClick.prototype.onMouse = function(event) { // ... };
    FastClick.prototype.onClick = function(event) { // ... };
    FastClick.prototype.onTouchCancel = function(event) { // ... };

    首先,按照我们预想的思路, 应该想看touchStart方法.

    FastClick.prototype.onTouchStart = function(event) {
        var targetElement, touch, selection;
    
        // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
        if (event.targetTouches.length > 1) {
    	return true;
         }
    
      targetElement = this.getTargetElementFromEventTarget(event.target);
      touch = event.targetTouches[0];
      if (deviceIsIOS) { // ... 此省略部分issue兼容代码 }
      this.trackingClick = true;
      this.trackingClickStart = event.timeStamp;
      this.targetElement = targetElement;
    
      this.touchStartX = touch.pageX;
      this.touchStartY = touch.pageY;
      
      // Prevent phantom clicks on fast double-tap (issue #36)
      if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
         event.preventDefault();
      }
      return true;
    }

    touchStart方法主要存储几个状态属性, trackingClick用于其他事件里面追逐,是否触发click以这个属性为准,trackingClickStart 用于存储点下去时候的时间戳, touchStartX, touchStartY分别为点击事件的X, Y轴。至于其他工作,比如忽略多指操作(即不追踪)。

    FastClick.prototype.onTouchHove = function(event) {
     // trackingClick是是否追踪,不追踪直接返回
      if (!this.trackingClick) {
        return true;
      }
    
      // 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;
    }
    
      return true;
    }

    这是touchMove的处理事件,主要就是两个判断,一个是如果move事件触发元素和touchStart的元素不是一个元素,即已经有移动,则不触发,touchHasMoved方法必然是用于算移动距离是否已经超过限制,超过则不追踪click。
    touchHasMoved实现如下:

    FastClick.prototype.touchHasMoved = function(event) {
      var touch = event.changedTouches[0], boundary = this.touchBoundary;
    
      if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
        return true;
      }
    
      return false;
    };

    这里有个值得了解的e.changedTouches属性, 属于touch event属性,学习了。

    接下来就是最后一步,touchEnd,按我们预计,最后应该是模拟click事件。我看了下,整个end事件处理很多东西,但是最终其实它只做一件事情,关于其兼容判断,我暂时删除。删除完的代码如下:

    FastClick.prototype.onTouchEnd = function (event) {
      if (!this.trackingClick) {
        return true;
      }
      
      // 模拟click事件
      this.sendClick(targetElement, event);
    }

    到这里流程其实已经结束了,最后我们还可以看下如何去模拟click实现,关于sendClick,fastClick是这样实现的:

    FastClick.prototype.sendClick = function(targetElement, event) {
      var clickEvent, touch;
    
      // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
      if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
      }
    
      touch = event.changedTouches[0];
    
    		// Synthesise a click event, with an extra attribute so it can be tracked
      clickEvent = document.createEvent('MouseEvents');
      clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
      clickEvent.forwardedTouchEvent = true;
      targetElement.dispatchEvent(clickEvent);
    };
    
    FastClick.prototype.determineEventType = function(targetElement) {
    
    		//Issue #159: Android Chrome Select Box does not open with a synthetic click event
      if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
        return 'mousedown';
      }
    
      return 'click';
    };  

    实际上,就是通过DOM接口的createEvent创建一个模拟事件,叫做click覆盖默认的click,这个事件依然会冒泡,依然可以委托。这就是整个fastclick的思路,到这里,就结束了。但是fastclick还有很多兼容性工作,感兴趣的可以移步到fastclick仓库拜读。

  • 相关阅读:
    SEO搜索引擎
    SEO
    编程的智慧
    ES6编程规范
    面试题集
    motto
    motto
    JS
    motto
    Linux
  • 原文地址:https://www.cnblogs.com/wuweixin/p/11393082.html
Copyright © 2011-2022 走看看