所谓 zepto 的 touch 其实就是指这个文件啦,能够看到区区 165 行(包括注释)就完成了 swipe 和 tap 相关的事件实现。在正式开始分析源码以前,咱们先说说 touch 相关的几个事件,由于不管是 tap 仍是 swipe 都是基于他们的。javascript
touchstart 触摸屏幕的瞬间java
touchmove 手指在屏幕上的移动过程一直触发git
touchend 离开屏幕的瞬间github
touchcancel 触摸取消(取决于浏览器实现,并不经常使用)浏览器
触摸屏下事件触发顺序是app
touchstart -> touchmove -> touchend -> click
click事件在移动端上会有 300ms 的延迟,同时由于须要 长按
,双触击
等富交互,因此咱们一般都会引入相似 zepto 这样的库。zepto 实现了'swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'
这样一些功能。dom
咱们直接看到 touch 源码的 49 行,从这里开始就是上述事件的实现了。不难想到 MSGesture 是对 mobile ie 的实现,本文不作讨论。往下面看到 66 行,$(document).on('touchstart MSPointerDown pointerdown')
开始。ide
//判断事件类型是否为 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 本身的事件体系来注册和触发。
咱们在聊到移动端 js 方案的时候很容易听到这二者,但我我的认为这二者是没法对比的。缘由以下:zepto 是一个移动端的 js 库,而 fastclick 专一于 click 在移动端的触发问题。fastclick 的 github 主页上有一句话是“Polyfill to remove click delays on browsers with touch UIs”,翻译过来就是干掉移动端 click 延时的补丁。这个延时就是咱们在引入 touch 的背景里提到过。
不肯意下代码的能够直接点这里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 在触摸屏下的使用,引入以后再初始化一次就行了,很适合复用代码的情景。
本文中 zepto 和 fastclick 都有用到 touchEvent,可是 zepto 当中用的是 e.touches
而 fastclick 却用的是 e.targetTouches
。这二者的差别咱们来一点一点地扒。
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者能够检测触点的移动,触点的增长和减小,等等。
属性:
TouchEvent.changedTouches
一个 TouchList 对象,包含了表明全部从上一次触摸事件到这次事件过程当中,状态发生了改变的触点的 Touch 对象。
TouchEvent.targetTouches
一个 TouchList 对象,是包含了以下触点的 Touch 对象:触摸起始于当前事件的目标 element 上,而且仍然没有离开触摸平面的触点.
TouchEvent.touches
一个 TouchList 对象,包含了全部当前接触触摸平面的触点的 Touch 对象,不管它们的起始于哪一个 element 上,也不管它们状态是否发生了变化。
TouchEvent.type
这次触摸事件的类型,可能值为 touchstart, touchmove, touchend 等等
TouchEvent.target
触摸事件的目标 element,这个目标元素对应 TouchEvent.changedTouches
中的触点的起始元素。
TouchEvent.altKey, TouchEvent.ctrlKey, TouchEvent.metaKey, TouchEvent.shiftKey
触摸事件触发时,键盘对应的键(例如 alt )是否被按下。
TouchList 就是一系列的 Touch,经过 TouchList.length
能够知道当前有几个触点,TouchList[0]
或者 TouchList.item(0)
用来访问第一个触点。
属性
Touch.identifier
:touch 的惟一标志,整个 touch 过程当中(也就是 end 以前)不会改变
Touch.screenX
和 Touch.screenY
:坐标原点为屏幕左上角
Touch.clientX
和 Touch.clientY
:坐标原点在当前可视区域左上角,这两个值不包含滚动偏移
Touch.pageX
和 Touch.pageY
:坐标原点在HTML文档左上角,这两个值包含了水平滚动的偏移
Touch.radiusX
和 Touch.radiusY
:触摸平面的最小椭圆的水平轴(X轴)半径和垂直轴(Y轴)半径
Touch.rotationAngle
:触摸平面的最小椭圆与水平轴顺时针夹角
Touch.force
:压力值 0.0-1.0
Touch.target
:Touch相关事件触发时的 element 不会随 move 变化。若是 move 当中该元素被删掉,这个 target 依然会不变,但不会冒泡。最佳实践是将触摸事件的监听器绑定到这个元素自己, 防止元素被移除后, 没法再从它的上一级元素上侦测到从该元素冒泡的事件。
但愿本文能解答一些你们在移动端开发当中的一些问题,本文行文匆忙若有不正确的地方但愿能回复告知。