Zepto.js touch模塊深刻分析 解決手機點擊事件

源碼:

// 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) 
View Code

分析:

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; } 

二、處理手指長按

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)

// 若是手指移動屏幕超過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 ?javascript

條件1:手指移動不超過30像素java

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了,可是還考慮了另外一種狀況,手指滑動屏幕後又滑動到起始點,那麼:sql

!Math.abs(touch.x1 - touch.x2) > 30) === !Math.abs(touch.y1 - touch.y2) > 30) === true;

爲了避免觸發tap事件,這裏又加了條件限制,理解這點很重要this

if (deltaX < 30 && deltaY < 30) { // handle tap }

注意:spa

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的計算,你懂得...orm

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 

六、 快捷註冊事件ip

['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事件都不會被觸發,慎重慎重...,

相關文章
相關標籤/搜索