浅谈FastClick 填坑及源码解析(4)

顺道看下这里的 this.updateScrollParent:


  /**
   * 检查target是否一个滚动容器里的子元素,如果是则给它加个标记
   */
  FastClick.prototype.updateScrollParent = function(targetElement) {
    var scrollParent, parentElement;
    scrollParent = targetElement.fastClickScrollParent;
    if (!scrollParent || !scrollParent.contains(targetElement)) {
      parentElement = targetElement;
      do {
        if (parentElement.scrollHeight > parentElement.offsetHeight) {
          scrollParent = parentElement;
          targetElement.fastClickScrollParent = parentElement;
          break;
        }
        parentElement = parentElement.parentElement;
      } while (parentElement);
    }
    // 给滚动容器加个标志fastClickLastScrollTop,值为其当前垂直滚动偏移
    if (scrollParent) {
      scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
    }
  };

另外要注意的是,在 onTouchStart 里被标记为 true 的 this.trackingClick 属性,都会在其它事件回调(比如 ontouchmove )的开头做检测,如果没被赋值过,则直接忽略:

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

当然在 ontouchend 事件里会把它重置为 false。

2. this.onTouchMove

这段代码量好少:

  FastClick.prototype.onTouchMove = function(event) {
    //不是需要被追踪click的事件则忽略
    if (!this.trackingClick) {
      return true;
    }
    // 如果target突然改变了,或者用户其实是在移动手势而非想要click
    // 则应该清掉this.trackingClick和this.targetElement,告诉后面的事件你们也不用处理了
    if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
      this.trackingClick = false;
      this.targetElement = null;
    }
    return true;
  };

看下这里用到的 this.touchHasMoved 原型方法:

  //判断是否移动了
  //this.touchBoundary是常量,值为10
  //如果touch已经移动了10个偏移量单位,则应当作为移动事件处理而非click事件
  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;
  };

3. onTouchEnd


  FastClick.prototype.onTouchEnd = function(event) {
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
    if (!this.trackingClick) {
      return true;
    }
    // 避免 phantom 的双击(200ms内快速点了两次)触发 click
    // 我们在 ontouchstart 里已经做过一次判断了(仅仅禁用默认事件),这里再做一次判断
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
      this.cancelNextClick = true; //该属性会在 onMouse 事件中被判断,为true则彻底禁用事件和冒泡
      return true;
    }
    //this.tapTimeout是常量,值为700
    //识别是否为长按事件,如果是(大于700ms)则忽略
    if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
      return true;
    }
    // 得重置为false,避免input事件被意外取消
    // 例子见 https://github.com/ftlabs/fastclick/issues/156
    this.cancelNextClick = false;
    this.lastClickTime = event.timeStamp; //标记touchend时间,方便下一次的touchstart做双击校验
    trackingClickStart = this.trackingClickStart;
    //重置 this.trackingClick 和 this.trackingClickStart
    this.trackingClick = false;
    this.trackingClickStart = 0;
    // iOS 6.0-7.*版本下有个问题 —— 如果layer处于transition或scroll过程,event所提供的target是不正确的
    // 所以咱们得重找 targetElement(这里通过 document.elementFromPoint 接口来寻找)
    if (deviceIsIOSWithBadTarget) { //iOS 6.0-7.*版本
      touch = event.changedTouches[0]; //手指离开前的触点
      // 有些情况下 elementFromPoint 里的参数是预期外/不可用的, 所以还得避免 targetElement 为 null
      targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
      // target可能不正确需要重找,但fastClickScrollParent是不会变的
      targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
    }
    targetTagName = targetElement.tagName.toLowerCase();
    if (targetTagName === 'label') { //是label则激活其指向的组件
      forElement = this.findControl(targetElement);
      if (forElement) {
        this.focus(targetElement);
        //安卓直接返回(无需合成click事件触发,因为点击和激活元素不同,不存在点透)
        if (deviceIsAndroid) {
          return false;
        }
        targetElement = forElement;
      }
    } else if (this.needsFocus(targetElement)) { //非label则识别是否需要focus的元素
      //手势停留在组件元素时长超过100ms,则置空this.targetElement并返回
      //(而不是通过调用this.focus来触发其聚焦事件,走的原生的click/focus事件触发流程)
      //这也是为何文章开头提到的问题中,稍微久按一点(超过100ms)textarea是可以把光标定位在正确的地方的原因
      //另外iOS下有个意料之外的bug——如果被点击的元素所在文档是在iframe中的,手动调用其focus的话,
      //会发现你往其中输入的text是看不到的(即使value做了更新),so这里也直接返回
      if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
        this.targetElement = null;
        return false;
      }
      this.focus(targetElement);
      this.sendClick(targetElement, event); //立即触发其click事件,而无须等待300ms
      //iOS4下的 select 元素不能禁用默认事件(要确保它能被穿透),否则不会打开select目录
      //有时候 iOS6/7 下(VoiceOver开启的情况下)也会如此
      if (!deviceIsIOS || targetTagName !== 'select') {
        this.targetElement = null;
        event.preventDefault();
      }

      return false;
    }
    if (deviceIsIOS && !deviceIsIOS4) {
      // 滚动容器的垂直滚动偏移改变了,说明是容器在做滚动而非点击,则忽略
      scrollParent = targetElement.fastClickScrollParent;
      if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
        return true;
      }
    }
    // 查看元素是否无需处理的白名单内(比如加了名为“needsclick”的class)
    // 不是白名单的则照旧预防穿透处理,立即触发合成的click事件
    if (!this.needsClick(targetElement)) {
      event.preventDefault();
      this.sendClick(targetElement, event);
    }
    return false;
  };

      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/368.html