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