目的:記錄 Zepto.js touch模塊 源碼閱讀ide
源碼:this
// Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT license. ; (function($) { var touch = {}, touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, longTapDelay = 750, gesture function swipeDirection(x1, x2, y1, y2) { return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down') } function longTap() { longTapTimeout = null if (touch.last) { touch.el.trigger('longTap') touch = {} } } function cancelLongTap() { if (longTapTimeout) clearTimeout(longTapTimeout) longTapTimeout = null } function cancelAll() { if (touchTimeout) clearTimeout(touchTimeout) if (tapTimeout) clearTimeout(tapTimeout) if (swipeTimeout) clearTimeout(swipeTimeout) if (longTapTimeout) clearTimeout(longTapTimeout) touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null touch = {} } function isPrimaryTouch(event) { return (event.pointerType == 'touch' || event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary } function isPointerEventType(e, type) { return (e.type == 'pointer' + type || e.type.toLowerCase() == 'mspointer' + type) } $(document).ready(function() { var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType if ('MSGesture' in window) { gesture = new MSGesture() gesture.target = document.body } $(document) .bind('MSGestureEnd', function(e) { var swipeDirectionFromVelocity = e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null; if (swipeDirectionFromVelocity) { touch.el.trigger('swipe') touch.el.trigger('swipe' + swipeDirectionFromVelocity) } }) .on('touchstart MSPointerDown pointerdown', function(e) { if ((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0] if (e.touches && e.touches.length === 1 && touch.x2) { // Clear out touch movement data if we have it sticking around // This can occur if touchcancel doesn't fire due to preventDefault, etc. touch.x2 = undefined touch.y2 = undefined } now = Date.now() delta = now - (touch.last || now) touch.el = $('tagName' in firstTouch.target ? firstTouch.target : firstTouch.target.parentNode) touchTimeout && clearTimeout(touchTimeout) touch.x1 = firstTouch.pageX touch.y1 = firstTouch.pageY if (delta > 0 && delta <= 250) touch.isDoubleTap = true touch.last = now longTapTimeout = setTimeout(longTap, longTapDelay) // adds the current touch contact for IE gesture recognition if (gesture && _isPointerType) gesture.addPointer(e.pointerId); }) .on('touchmove MSPointerMove pointermove', function(e) { if ((_isPointerType = isPointerEventType(e, 'move')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0] cancelLongTap() touch.x2 = firstTouch.pageX touch.y2 = firstTouch.pageY deltaX += Math.abs(touch.x1 - touch.x2) deltaY += Math.abs(touch.y1 - touch.y2) }) .on('touchend MSPointerUp pointerup', function(e) { if ((_isPointerType = isPointerEventType(e, 'up')) && !isPrimaryTouch(e)) return cancelLongTap() // swipe 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') touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) touch = {} }, 0) // normal tap else if ('last' in touch) // don't fire tap when delta position changed by more than 30 pixels, // for instance when moving to a point and back to origin if (deltaX < 30 && deltaY < 30) { // delay by one tick so we can cancel the 'tap' event if 'scroll' fires // ('tap' fires before 'scroll') tapTimeout = setTimeout(function() { // trigger universal 'tap' with the option to cancelTouch() // (cancelTouch cancels processing of single vs double taps for faster 'tap' response) var event = $.Event('tap') event.cancelTouch = cancelAll touch.el.trigger(event) // trigger double tap immediately if (touch.isDoubleTap) { if (touch.el) touch.el.trigger('doubleTap') touch = {} } // trigger single tap after 250ms of inactivity else { touchTimeout = setTimeout(function() { touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250) } }, 0) } else { touch = {} } deltaX = deltaY = 0 }) // when the browser window loses focus, // for example when a modal dialog is shown, // cancel all ongoing events .on('touchcancel MSPointerCancel pointercancel', cancelAll) // scrolling the window indicates intention of the user // to scroll, not tap or swipe, so cancel all ongoing events $(window).on('scroll', cancelAll) }) ; ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap' ].forEach(function(eventName) { $.fn[eventName] = function(callback) { return this.on(eventName, callback) } }) })(Zepto)
分析:spa
var now, delta, touch = {}; $(document) .on('touchstart', startListener) .on('touchmove', moveListener) .on('touchend', endListener);
一、是單擊仍是雙擊指針
function startListener(e){ now = Date.now(); delta = now - (touch.last || now); // 手指連續輕觸兩次,時間間隔大於0,小於等於.25s,則爲雙擊,反之單擊 if ( delta > 0 && delta <= 250 ) { touch.isDoubleTap = true; } touch.last = now; }
二、處理手指長按code
var longTapTimeout, longTapDelay = 750; function longTap() { longTapTimeout = null if (touch.last) { touch.el.trigger('longTap') touch = {} } } function cancelLongTap() { if (longTapTimeout) clearTimeout(longTapTimeout) longTapTimeout = null } function startListener(e){ // 默認就是長按,若是手指未移動和離開,超過.75s就觸發longTap longTapTimeout = setTimeout(longTap, longTapDelay) } function moveListener(e){ // 若是手指輕觸屏幕後未超過.75s,則取消手指長按監聽 longTapTimeout = setTimeout(longTap, longTapDelay) } function endListener(e){ // 若是手指輕觸屏幕後未超過.75s,則取消手指長按監聽 longTapTimeout = setTimeout(longTap, longTapDelay) }
三、是滑動(swipe)仍是輕觸(tap)orm
// 若是手指移動屏幕超過30像素,則觸發相應的滑動事件,swipeLeft, swipeRight, swipeUp, swipeDown function endListener(e){ // swipe 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') touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) touch = {} }, 0); } else { // handle tap // 關於處理tap事件,請看第四點 } }
四、輕觸 tap, singleTap, doubleTap對象
4.一、什麼時候觸發 tap ?blog
條件1:手指移動不超過30像素事件
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) { // swipe } else { // tap }
條件2:依據條件1,基本上能夠觸發tap了,可是還考慮了另外一種狀況,手指滑動屏幕後又滑動到起始點,那麼:ip
!Math.abs(touch.x1 - touch.x2) > 30) === !Math.abs(touch.y1 - touch.y2) > 30) === true;
爲了避免觸發tap事件,這裏又加了條件限制,理解這點很重要
if (deltaX < 30 && deltaY < 30) { // handle tap }
注意:
deltaX !== Math.abs(touch.x1 - touch.x2);
deltaY !== Math.abs(touch.y1 - touch.y2);
請看 moveListener 中的代碼:
function moveListener(e){ // ... touch.x2 = firstTouch.pageX touch.y2 = firstTouch.pageY deltaX += Math.abs(touch.x1 - touch.x2) deltaY += Math.abs(touch.y1 - touch.y2) }
例: deltaX的計算,你懂得...
if ( Math.abs(touch.x1 - touch.x2) === 10 ) { deltaX = 10 + 9 + 8 + ... + 0; }
4.二、處理tap,doubleTap,singleTap三者之間的關係
function cancelAll() { if (touchTimeout) clearTimeout(touchTimeout) if (tapTimeout) clearTimeout(tapTimeout) if (swipeTimeout) clearTimeout(swipeTimeout) if (longTapTimeout) clearTimeout(longTapTimeout) touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null // 這句很重要,將影響全部須要對touch對象屬性判斷的語句 touch = {} } function endListener(e){ tapTimeout = setTimeout(function() { var event = $.Event('tap') // tap事件對象event能夠取消後續綁定的doubleTap, singleTap處理器 event.cancelTouch = cancelAll touch.el.trigger(event) // 當即觸發雙擊事件 if (touch.isDoubleTap) { if (touch.el) touch.el.trigger('doubleTap') touch = {} } // 定時.25s後再觸發單擊事件 else { touchTimeout = setTimeout(function() { touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250) } }, 0) }
例如:如何在tap事件處理器中取消 doubleTap或singleTap事件監聽器
$('body') .on('tap', function(e){ console.log('tap'); // 執行下面語句將影響是否觸發綁定的 doubleTap或singleTap 處理器 e.cancelTouch(); }) .on('doubleTap', function(e){ console.log('doubleTap'); }) .on('singleTap', function(e){ console.log('singleTap'); }); // 'tap'
五、兼容指針事件系統
// 判斷是不是指針事件類型 function isPointerEventType(e, type) { return (e.type == 'pointer' + type || e.type.toLowerCase() == 'mspointer' + type) } // 判斷是不是第一個touch或pointer事件對象 function isPrimaryTouch(event) { return (event.pointerType == 'touch' || event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary } // 若是是指針類型是 pointerdown 或 pointermove 或 pointerup 且 不是第一個touch 或 pointer 事件對象,返回空, // 直接屏蔽了第二個、第三...的觸摸處理 if ((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return if ((_isPointerType = isPointerEventType(e, 'move')) && !isPrimaryTouch(e)) return if ((_isPointerType = isPointerEventType(e, 'up')) && !isPrimaryTouch(e)) return
六、快捷註冊事件
['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap' ].forEach(function(eventName) { $.fn[eventName] = function(callback) { return this.on(eventName, callback) } });
你能夠用 on 方法註冊事件,也能夠快捷註冊,下面兩種方式都是同樣的,相似jQuery用法
$('body').on('tap', function(){ console.log('body trigger tap event'); }); $('body').tap(function(){ console.log('body trigger tap event'); });
篇尾總結:
源碼中大部分代碼都已經解析完畢,若有不合理的地方,還請賜教,touch模塊的中全部的事件都支持冒泡,可是不會對原生的touch事件產生影響,另外全部元素綁定的事件都是在文檔document元素的touchend處理中觸發,
若是頁面中有一元素在原生touch事件中阻止了冒泡,那麼頁面中全部元素註冊的 zepto touch事件都不會被觸發,慎重慎重...,至此完畢,感謝閱讀!!