摘要:在觸發(fā)事件前,先將保存定時器的變量釋放,如果對象中存在,則觸發(fā)事件,保存的是最后觸摸的時間。如果有觸發(fā)的定時器,清除定時器即可阻止事件的觸發(fā)。其實就是清除所有相關(guān)的定時器,最后將對象設(shè)置為。進入時,立刻清除定時器的執(zhí)行。
大家都知道,因為歷史原因,移動端上的點擊事件會有 300ms 左右的延遲,Zepto 的 touch 模塊解決的就是移動端點擊延遲的問題,同時也提供了滑動的 swipe 事件。
讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
GitBook《reading-zepto》
實現(xiàn)的事件;["swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap", "singleTap", "longTap"].forEach(function(eventName){ $.fn[eventName] = function(callback){ return this.on(eventName, callback) } })
從上面的代碼中可以看到,Zepto 實現(xiàn)了以下的事件:
swipe: 滑動事件
swipeLeft: 向左滑動事件
swipeRight: 向右滑動事件
swipeUp: 向上滑動事件
swipeDown: 向下滑動事件
doubleTap: 屏幕雙擊事件
tap: 屏幕點擊事件,比 click 事件響應(yīng)更快
singleTap: 屏幕單擊事件
longTap: 長按事件
并且為每個事件都注冊了快捷方法。
內(nèi)部方法 swipeDirectionfunction swipeDirection(x1, x2, y1, y2) { return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "Left" : "Right") : (y1 - y2 > 0 ? "Up" : "Down") }
返回的是滑動的方法。
x1 為 x軸 起點坐標, x2 為 x軸 終點坐標, y1 為 y軸 起點坐標, y2 為 y軸 終點坐標。
這里有多組三元表達式,首先對比的是 x軸 和 y軸 上的滑動距離,如果 x軸 的滑動距離比 y軸 大,則為左右滑動,否則為上下滑動。
在 x軸 上,如果起點位置比終點位置大,則為向左滑動,返回 Left ,否則為向右滑動,返回 Right 。
在 y軸 上,如果起點位置比終點位置大,則為向上滑動,返回 Up ,否則為向下滑動,返回 Down 。
longTapvar touch = {}, touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, longTapDelay = 750, gesture function longTap() { longTapTimeout = null if (touch.last) { touch.el.trigger("longTap") touch = {} } }
觸發(fā)長按事件。
touch 對象保存的是觸摸過程中的信息。
在觸發(fā) longTap 事件前,先將保存定時器的變量 longTapTimeout 釋放,如果 touch 對象中存在 last ,則觸發(fā) longTap 事件, last 保存的是最后觸摸的時間。最后將 touch 重置為空對象,以便下一次使用。
cancelLongTapfunction cancelLongTap() { if (longTapTimeout) clearTimeout(longTapTimeout) longTapTimeout = null }
撤銷 longTap 事件的觸發(fā)。
如果有觸發(fā) longTap 的定時器,清除定時器即可阻止 longTap 事件的觸發(fā)。
最后同樣需要將 longTapTimeout 變量置為 null ,等待垃圾回收。
cancelAllfunction cancelAll() { if (touchTimeout) clearTimeout(touchTimeout) if (tapTimeout) clearTimeout(tapTimeout) if (swipeTimeout) clearTimeout(swipeTimeout) if (longTapTimeout) clearTimeout(longTapTimeout) touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null touch = {} }
清除所有事件的執(zhí)行。
其實就是清除所有相關(guān)的定時器,最后將 touch 對象設(shè)置為 null 。
isPrimaryTouchfunction isPrimaryTouch(event){ return (event.pointerType == "touch" || event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary }
是否為主觸點。
當(dāng) pointerType 為 touch 并且 isPrimary 為 true 時,才為主觸點。 pointerType 可為 touch 、 pen 和 mouse ,這里只處理手指觸摸的情況。
isPointerEventTypefunction isPointerEventType(e, type){ return (e.type == "pointer"+type || e.type.toLowerCase() == "mspointer"+type) }
觸發(fā)的是否為 pointerEvent 。
在低版本的移動端 IE 瀏覽器中,只實現(xiàn)了 PointerEvent ,并沒有實現(xiàn) TouchEvent ,所以需要這個來判斷。
事件觸發(fā) 整體分析$(document).ready(function(){ var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType $(document) .bind("MSGestureEnd", function(e){ ... }) .on("touchstart MSPointerDown pointerdown", function(e){ ... }) .on("touchmove MSPointerMove pointermove", function(e){ ... }) .on("touchend MSPointerUp pointerup", function(e){ ... }) .on("touchcancel MSPointerCancel pointercancel", cancelAll) $(window).on("scroll", cancelAll)
先來說明幾個變量,now 用來保存當(dāng)前時間, delta 用來保存兩次觸摸之間的時間差, deltaX 用來保存 x軸 上的位移, deltaY 來用保存 y軸 上的位移, firstTouch 保存初始觸摸點的信息, _isPointerType 保存是否為 pointerEvent 的判斷結(jié)果。
從上面可以看到, Zepto 所觸發(fā)的事件,是從 touch 、 pointer 或者 IE 的 guesture 事件中,根據(jù)不同情況計算出來的。這些事件都綁定在 document 上。
IE Gesture 事件的處理IE 的手勢使用,需要經(jīng)歷三步:
創(chuàng)建手勢對象
指定目標元素
指定手勢識別時需要處理的指針
if ("MSGesture" in window) { gesture = new MSGesture() gesture.target = document.body }
這段代碼包含了前兩步。
on("touchstart MSPointerDown pointerdown", function(e){ ... if (gesture && _isPointerType) gesture.addPointer(e.pointerId) }
這段是第三步,用 addPointer 的方法,指定需要處理的指針。
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) } })
接下來就是分析手勢了,Gesture 里只處理 swipe 事件。
velocityX 和 velocityY 分別為 x軸 和 y軸 上的速率。這里以 1 或 -1 為臨界點,判斷 swipe 的方向。
如果 swipe 的方向存在,則觸發(fā) swipe 事件,同時也觸發(fā)帶方向的 swipe 事件。
starton("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) { 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) if (gesture && _isPointerType) gesture.addPointer(e.pointerId) })過濾掉非觸屏事件
if((_isPointerType = isPointerEventType(e, "down")) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0]
這里還將 isPointerEventType 的判斷結(jié)果保存到了 _isPointerType 中,用來判斷是否為 PointerEvent 。
這里的判斷其實就是只處理 PointerEvent 和 TouchEvent ,并且 TouchEvent 的 isPrimary 必須為 true 。
因為 TouchEvent 支持多點觸碰,這里只取觸碰的第一點存入 firstTouch 變量。
重置終點坐標if (e.touches && e.touches.length === 1 && touch.x2) { touch.x2 = undefined touch.y2 = undefined }
如果還需要記錄,終點坐標是需要更新的。
正常情況下,touch 對象會在 touchEnd 或者 cancel 的時候清空,但是如果用戶自己調(diào)用了 preventDefault 等,就可能會出現(xiàn)沒有清空的情況。
這里有一點不太明白,為什么只會在 touches 單點操作的時候才清空呢?多個觸碰點的時候不需要清空嗎?
記錄觸碰點的信息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
now 用來保存當(dāng)前時間。
delta 用來保存兩次點擊時的時間間隔,用來處理雙擊事件。
touch.el 用來保存目標元素,這里有個判斷,如果 target 不是標簽節(jié)點時,取父節(jié)點作為目標元素。這會在點擊偽類元素時出現(xiàn)。
如果 touchTimeout 存在,則清除定時器,避免重復(fù)觸發(fā)。
touch.x1 和 touch.y1 分別保存 x軸 坐標和 y軸 坐標。
雙擊事件if (delta > 0 && delta <= 250) touch.isDoubleTap = true
可以很清楚地看到, Zepto 將兩次點擊的時間間隔小于 250ms 時,作為 doubleTap 事件處理,將 isDoubleTap 設(shè)置為 true 。
長按事件touch.last = now longTapTimeout = setTimeout(longTap, longTapDelay)
將 touch.last 設(shè)置為當(dāng)前時間。這樣就可以記錄兩次點擊時的時間差了。
同時開始長按事件定時器,從上面的代碼可以看到,長按事件會在 750ms 后觸發(fā)。
moveon("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) })
move 事件處理了兩件事,一是記錄終點坐標,一是計算起點到終點之間的位移。
要注意這里還調(diào)用了 cancelLongTap 清除了長按定時器,避免長按事件的觸發(fā)。因為有移動,肯定就不是長按了。
endon("touchend MSPointerUp pointerup", function(e){ if((_isPointerType = isPointerEventType(e, "up")) && !isPrimaryTouch(e)) return cancelLongTap() if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) swipeTimeout = setTimeout(function() { if (touch.el){ touch.el.trigger("swipe") touch.el.trigger("swipe" + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) } touch = {} }, 0) else if ("last" in touch) if (deltaX < 30 && deltaY < 30) { tapTimeout = setTimeout(function() { var event = $.Event("tap") event.cancelTouch = cancelAll if (touch.el) touch.el.trigger(event) if (touch.isDoubleTap) { if (touch.el) touch.el.trigger("doubleTap") touch = {} } else { touchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger("singleTap") touch = {} }, 250) } }, 0) } else { touch = {} } deltaX = deltaY = 0 })swipe
cancelLongTap() if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) swipeTimeout = setTimeout(function() { if (touch.el){ touch.el.trigger("swipe") touch.el.trigger("swipe" + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) } touch = {} }, 0)
進入 end 時,立刻清除 longTap 定時器的執(zhí)行。
可以看到,起點和終點的距離超過 30 時,會被判定為 swipe 滑動事件。
在觸發(fā)完 swipe 事件后,立即觸發(fā)對應(yīng)方向上的 swipe 事件。
注意,swipe 事件并不是在 end 系列事件觸發(fā)時立即觸發(fā)的,而是設(shè)置了一個 0ms 的定時器,讓事件異步觸發(fā),這個有什么用呢?后面會講到。
tapelse if ("last" in touch) if (deltaX < 30 && deltaY < 30) { tapTimeout = setTimeout(function() { var event = $.Event("tap") event.cancelTouch = cancelAll if (touch.el) touch.el.trigger(event) }, 0) } else { touch = {} } deltaX = deltaY = 0
終于看到重點了,首先判斷 last 是否存在,從 start 中可以看到,如果觸發(fā)了 start , last 肯定是存在的,但是如果觸發(fā)了長按事件,touch 對象會被清空,這時不會再觸發(fā) tap 事件。
如果不是 swipe 事件,也不存在 last ,則只將 touch 清空,不觸發(fā)任何事件。
在最后會將 deltaX 和 deltaY 重置為 0 。
觸發(fā) tap 事件時,會在 event 中加了 cancelTouch 方法,外界可以通過這個方法取消所有事件的執(zhí)行。
這里同樣用了 setTimeout 異步觸發(fā)事件。
doubleTapif (touch.isDoubleTap) { if (touch.el) touch.el.trigger("doubleTap") touch = {} }
這個 isDoubleTap 在 start 時確定的,上面已經(jīng)分析過了,在 end 的時候觸發(fā) doubleTap 事件。
因此,可以知道,在觸發(fā) doubleTap 事件之前會觸發(fā)兩次 tap 事件。
singleTaptouchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger("singleTap") touch = {} }, 250)
如果不是 doubleTap ,會在 tap 事件觸發(fā)的 250ms 后,觸發(fā) singleTap 事件。
cancel.on("touchcancel MSPointerCancel pointercancel", cancelAll)
在接受到 cancel 事件時,調(diào)用 cancelAll 方法,取消所有事件的觸發(fā)。
scroll$(window).on("scroll", cancelAll)
從前面的分析可以看到,所有的事件觸發(fā)都是異步的。
因為在 scroll 的時候,肯定是只想響應(yīng)滾動的事件,異步觸發(fā)是為了在 scroll 的過程中和外界調(diào)用 cancelTouch 方法時, 可以將事件取消。
系列文章讀Zepto源碼之代碼結(jié)構(gòu)
讀Zepto源碼之內(nèi)部方法
讀Zepto源碼之工具函數(shù)
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
讀Zepto源碼之操作DOM
讀Zepto源碼之樣式操作
讀Zepto源碼之屬性操作
讀Zepto源碼之Event模塊
讀Zepto源碼之IE模塊
讀Zepto源碼之Callbacks模塊
讀Zepto源碼之Deferred模塊
讀Zepto源碼之Ajax模塊
讀Zepto源碼之Assets模塊
讀Zepto源碼之Selector模塊
參考zepto touch 庫源碼分析
PointerEvent
Pointer events
TouchEvent
Touch
GestureEvent
MSGestureEvent
一步一步DIY zepto庫,研究zepto源碼8--touch模塊
zepto源碼學(xué)習(xí)-06 touch
zepto源碼之touch.js
addPointer method.aspx)
License署名-非商業(yè)性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最后,所有文章都會同步發(fā)送到微信公眾號上,歡迎關(guān)注,歡迎提意見:
作者:對角另一面
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/91962.html
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個判斷需要引入設(shè)備偵測模塊。然后是監(jiān)測事件,根據(jù)這三個事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
摘要:讀源碼系列文章已經(jīng)放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調(diào)用的時候,會為返回的結(jié)果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā)事件,提交表單。最終返回的結(jié)果是一個數(shù)組,每個數(shù)組項為包含和屬性的對象。否則手動綁定事件,如果沒有阻止瀏覽器的默認事件,則在第一個表單上觸發(fā),提交表單。 Form 模塊處理的是表單提交。表單提交包含兩部分,一部分是格式化表單數(shù)據(jù),另一部分是觸發(fā) submit 事件,提交表單。 讀 Zepto 源碼系列文章已...
摘要:所以模塊依賴于模塊,在引入前必須引入模塊。原有的方法分析見讀源碼之樣式操作方法首先調(diào)用原有的方法,將元素顯示出來,這是實現(xiàn)動畫的基本條件。如果沒有傳遞,或者為值,則表示不需要動畫,調(diào)用原有的方法即可。 fx 模塊提供了 animate 動畫方法,fx_methods 利用 animate 方法,提供一些常用的動畫方法。所以 fx_methods 模塊依賴于 fx 模塊,在引入 fx_m...
摘要:用法與參數(shù)要理解這段代碼,先來看一下的用法和參數(shù)用法參數(shù)回調(diào)函數(shù),有如下參數(shù)上一個回調(diào)函數(shù)返回的值或者是初始值當(dāng)前值當(dāng)前值在數(shù)組中的索引調(diào)用的數(shù)組初始值,如果沒有提供,則為數(shù)組的第一項。接下來,檢測回調(diào)函數(shù)是否為,如果不是,拋出類型錯誤。 IOS3 模塊是針對 IOS 的兼容模塊,實現(xiàn)了兩個常用方法的兼容,這兩個方法分別是 trim 和 reduce 。 讀 Zepto 源碼系列文章...
閱讀 3321·2021-09-22 15:58
閱讀 1784·2019-08-30 14:17
閱讀 1777·2019-08-28 18:05
閱讀 1571·2019-08-26 13:33
閱讀 739·2019-08-26 12:20
閱讀 668·2019-08-26 12:18
閱讀 3260·2019-08-26 11:59
閱讀 1461·2019-08-26 10:36