zepto touch 库源码剖析

所谓 zepto 的 touch 实在就是指这个文件啦,能够看到戋戋 165 行(包括解释)就完成了 swipe 和 tap 相干的事宜完成。在正式最先剖析源码之前,我们先说说 touch 相干的几个事宜,由于不管是 tap 照样 swipe 都是基于他们的。

touch 相干事宜

  1. touchstart 触摸屏幕的霎时

  2. touchmove 手指在屏幕上的挪动历程一向触发

  3. touchend 脱离屏幕的霎时

  4. touchcancel 触摸作废(取决于浏览器完成,并不经常运用)

触摸屏下事宜触发递次是

touchstart -> touchmove -> touchend -> click

引入 touch 的背景

click事宜在挪动端上会有 300ms 的耽误,同时由于须要 长按双触击 等富交互,所以我们一般都邑引入相似 zepto 如许的库。zepto 完成了'swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap' 如许一些功用。

zepto touch 源码

我们直接看到 touch 源码的 49 行,从这里最先就是上述事宜的完成了。不难想到 MSGesture 是对 mobile ie 的完成,本文不做议论。往下面看到 66 行,$(document).on('touchstart MSPointerDown pointerdown') 最先。

//推断事宜范例是不是为 touch
if((_isPointerType = isPointerEventType(e, 'down')) &&
  !isPrimaryTouch(e)) return
// touches 是触摸点的数目
firstTouch = _isPointerType ? e : e.touches[0]
if (e.touches && e.touches.length === 1 && touch.x2) {
  touch.x2 = undefined
  touch.y2 = undefined
}
// 记载第一次触摸的时刻
now = Date.now()
// 盘算本次触摸与末了一次的时刻差
delta = now - (touch.last || now)
// 查找 touch 事宜的 dom 
touch.el = $('tagName' in firstTouch.target ?
  firstTouch.target : firstTouch.target.parentNode)
// 假如 touchTimeout 存在就清算掉
touchTimeout && clearTimeout(touchTimeout)
// 记载当前坐标
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
// 触摸时刻差小于 250ms 则为 DoubleTap
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
// 记载实行后的时刻
touch.last = now
// 留一个长触摸,假如 touchmove 会把这个清算掉
longTapTimeout = setTimeout(longTap, longTapDelay)  

接下来是 $(document).on('touchmove MSPointerMove pointermove')

//推断事宜范例是不是为 move
if((_isPointerType = isPointerEventType(e, 'move')) &&
          !isPrimaryTouch(e)) return
firstTouch = _isPointerType ? e : e.touches[0]
// 一旦进入 move 就会清算掉 LongTap
cancelLongTap()
// 当前手指坐标
touch.x2 = firstTouch.pageX
touch.y2 = firstTouch.pageY
// x 轴和 y 轴的变化量 Math.abs 是取绝对值的意义
deltaX += Math.abs(touch.x1 - touch.x2)
deltaY += Math.abs(touch.y1 - touch.y2)

末了固然就是 $(document).on('touchend MSPointerUp pointerup') 了,这个也是全部 touch 最为庞杂的一部份。

if((_isPointerType = isPointerEventType(e, 'up')) &&
          !isPrimaryTouch(e)) return
        cancelLongTap()

    // 假如是 swipe,x 轴或许 y 轴挪动凌驾 30px
    if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
        (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))

      swipeTimeout = setTimeout(function() {
        touch.el.trigger('swipe')
        // swipeDirection 是推断 swipe 方向的
        touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
        touch = {}
      }, 0)

    // tap 事宜
    else if ('last' in touch)
      if (deltaX < 30 && deltaY < 30) {
         // tapTimeout 是为了 scroll 的时刻轻易消灭
        tapTimeout = setTimeout(function() {
          // 建立 tap 事宜,并增添 cancelTouch 要领
          var event = $.Event('tap')
          event.cancelTouch = cancelAll
          touch.el.trigger(event)

          // 触发 DoubleTap
          if (touch.isDoubleTap) {
            if (touch.el) touch.el.trigger('doubleTap')
            touch = {}
          }

          // singleTap (这个观点是相对于 DoubleTap 的,能够看看我们在最初的那段源码剖析中有如许一段 if (delta > 0 && delta <= 250) touch.isDoubleTap = true ,所以 250 ms 以后没有二次触摸的就算是 singleTap 了 
          else {
            touchTimeout = setTimeout(function(){
              touchTimeout = null
              if (touch.el) touch.el.trigger('singleTap')
              touch = {}
            }, 250)
          }
        }, 0)
      } else {
        touch = {}
      }
      deltaX = deltaY = 0

全部读下来实在就是对 touchstart, touchmove, touchend 做了一些封装和推断,然后经由过程 zepto 自身的事宜系统来注册和触发。

fastclick 对照 zepto

我们在聊到挪动端 js 计划的时刻很轻易听到这两者,但我个人认为这两者是没法对照的。缘由以下:zepto 是一个挪动端的 js 库,而 fastclick 专注于 click 在挪动端的触发题目。fastclick 的 github 主页上有一句话是“Polyfill to remove click delays on browsers with touch UIs”,翻译过来就是干掉挪动端 click 延时的补丁。这个延时就是我们在引入 touch 的背景里提到过。

fastclick 源码剖析

不肯意下代码的能够直接点这里github地点起首赞一下 fastclick 的代码解释,异常全。

fastclick 的运用异常简朴,直接 FastClick.attach(document.body); 一句话搞定。所以源码剖析就从 attach 要领来看吧,824 行

    FastClick.attach = function(layer, options) {
        // 返回 FastClick 实例 layer 是一个 element 一般是 document.body ,options 天然就是设置了
        return new FastClick(layer, options);
    };

接下来回到 23 行看到 FastClick 组织函数,

 // 要领绑定,兼容老版本的安卓
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);
}
 // 事宜处置惩罚绑定部份
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') {
            adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
                if (!event.propagationStopped) {
                    callback(event);
                }
            }), capture);
        } else {
            adv.call(layer, type, callback, capture);
        }
    };
}

// 假如 layer 有 onclick ,就把 onclick 转换为 addEventListener 的体式格局
if (typeof layer.onclick === 'function') {
    oldOnClick = layer.onclick;
    layer.addEventListener('click', function(event) {
        oldOnClick(event);
    }, false);
    layer.onclick = null;
}

FastClick.prototype.onTouchStart 和 zepto 一样做了一些参数的记载,所以我这里就直接跳到 FastClick.prototype.onTouchEnd 看 fastclick 的中心。

FastClick.prototype.onTouchEnd = function(event) {
    var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;

    if (!this.trackingClick) {
        return true;
    }
    // 防备 double tap 的时刻距离内 click 触发
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
        this.cancelNextClick = true;
        return true;
    }
    // 超越 longtap 的时刻
    if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
        return true;
    }

    this.cancelNextClick = false;
    // 记载当前时刻
    this.lastClickTime = event.timeStamp;

    trackingClickStart = this.trackingClickStart;
    this.trackingClick = false;
    this.trackingClickStart = 0;

    if (deviceIsIOSWithBadTarget) {
        touch = event.changedTouches[0];
        targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
        targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
    }
    // 猎取 targetTagName 上面的一段是 targetTagName 兼容性
    targetTagName = targetElement.tagName.toLowerCase();
    // 处理 label for
    if (targetTagName === 'label') {
        forElement = this.findControl(targetElement);
        if (forElement) {
            this.focus(targetElement);
            if (deviceIsAndroid) {
                return false;
            }

            targetElement = forElement;
        }
    } else if (this.needsFocus(targetElement)) {
        if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
            this.targetElement = null;
            return false;
        }
        // 处理 input focus 
        this.focus(targetElement);
        // 触发 sendClick
        this.sendClick(targetElement, event);

        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;
        }
    }
    // 末了就来触发 sendClick 了
    if (!this.needsClick(targetElement)) {
        event.preventDefault();
        this.sendClick(targetElement, event);
    }

    return false;
};

看完上面的代码,我们马上来解读 FastClick.prototype.sendClick

FastClick.prototype.sendClick = function(targetElement, event) {
    var clickEvent, touch;
    // 拿触摸的第一个手指
    touch = event.changedTouches[0];
    // 自定义 clickEvent 事宜
    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;
    // 触发 clickEvent 事宜
    targetElement.dispatchEvent(clickEvent);
};

到此 fastclick 重要的东西我们就看得差不多了,代码当中不难看到 fastclick 的兼容性做的很好。它的重要目的是处理 click 在触摸屏下的运用,引入以后再初始化一次就好了,很合适复用代码的情形。

扩大讲一下 touchEvent

本文中 zepto 和 fastclick 都有用到 touchEvent,然则 zepto 当顶用的是 e.touches 而 fastclick 却用的是 e.targetTouches。这两者的差别我们来一点一点地扒。

TouchEvent 是一类形貌手指在触摸平面(触摸屏、触摸板等)的状况变化的事宜。这类事宜用于形貌一个或多个触点,使开辟者能够检测触点的挪动,触点的增添和削减,等等。

属性:

  1. TouchEvent.changedTouches 一个 TouchList 对象,包括了代表一切从上一次触摸事宜到此次事宜历程当中,状况发生了转变的触点的 Touch 对象。

  2. TouchEvent.targetTouches 一个 TouchList 对象,是包括了以下触点的 Touch 对象:触摸肇端于当前事宜的目的 element 上,而且依旧没有脱离触摸平面的触点.

  3. TouchEvent.touches 一个 TouchList 对象,包括了一切当前打仗触摸平面的触点的 Touch 对象,不管它们的肇端于哪一个 element 上,也不管它们状况是不是发生了变化。

  4. TouchEvent.type 此次触摸事宜的范例,能够值为 touchstart, touchmove, touchend 等等

  5. TouchEvent.target 触摸事宜的目的 element,这个目的元素对应 TouchEvent.changedTouches 中的触点的肇端元素。

  6. TouchEvent.altKey, TouchEvent.ctrlKey, TouchEvent.metaKey, TouchEvent.shiftKey 触摸事宜触发时,键盘对应的键(比方 alt )是不是被按下。

TouchList 与 Touch

TouchList 就是一系列的 Touch,经由过程 TouchList.length 能够晓得当前有几个触点,TouchList[0] 或许 TouchList.item(0) 用来接见第一个触点。

属性

  1. Touch.identifier:touch 的唯一标志,全部 touch 历程当中(也就是 end 之前)不会转变

  2. Touch.screenXTouch.screenY:坐标原点为屏幕左上角

  3. Touch.clientXTouch.clientY:坐标原点在当前可视地区左上角,这两个值不包括转动偏移

  4. Touch.pageXTouch.pageY:坐标原点在HTML文档左上角,这两个值包括了程度转动的偏移

  5. Touch.radiusXTouch.radiusY:触摸平面的最小椭圆的程度轴(X轴)半径和垂直轴(Y轴)半径

  6. Touch.rotationAngle:触摸平面的最小椭圆与程度轴顺时针夹角

  7. Touch.force:压力值 0.0-1.0

  8. Touch.target:Touch相干事宜触发时的 element 不会随 move 变化。假如 move 当中该元素被删掉,这个 target 依旧会稳定,但不会冒泡。最好实践是将触摸事宜的监听器绑定到这个元素自身, 防备元素被移除后, 没法再从它的上一级元素上侦测到从该元素冒泡的事宜。

愿望本文能解答一些人人在挪动端开辟当中的一些题目,本文行文急忙若有不正确的处所愿望能复兴示知。

    原文作者:flashback
    原文地址: https://segmentfault.com/a/1190000005882908
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞