epto 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
接下来是 $(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 轴移动超过 30pxif ((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  0 && delta 

整个读下来其实就是对 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
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.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 forif (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.screenX 和 Touch.screenY: 坐标原点为屏幕左上角

3.Touch.clientX 和 Touch.clientY: 坐标原点在当前可视区域左上角,这两个值不包含滚动偏移。

4.Touch.pageX 和 Touch.pageY: 坐标原点在HTML文档左上角,这两个值包含了水平滚动的偏移

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

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

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

8.Touch.target: Touch相关事件触发时的 element 不会随 move 变化。如果 move 当中该元素被删掉,这个 target 依然会不变,但不会冒泡。最佳实践是将触摸事件的监听器绑定到这个元素本身, 防止元素被移除后, 无法再从它的上一级元素上侦测到从该元素冒泡的事件。

希望本文能解答一些大家在移动端开发当中的一些问题,本文行文匆忙如有不正确的地方希望能回复告知。

关键字:JavaScript, touch, fastclick, false

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部