摘要:源碼分析不愿意下代碼的可以直接點(diǎn)這里地址首先贊一下的代碼注釋,非常全。屬性一個(gè)對(duì)象,包含了代表所有從上一次觸摸事件到此次事件過程中,狀態(tài)發(fā)生了改變的觸點(diǎn)的對(duì)象。
所謂 zepto 的 touch 其實(shí)就是指這個(gè)文件啦,可以看到區(qū)區(qū) 165 行(包括注釋)就完成了 swipe 和 tap 相關(guān)的事件實(shí)現(xiàn)。在正式開始分析源碼之前,我們先說說 touch 相關(guān)的幾個(gè)事件,因?yàn)闊o論是 tap 還是 swipe 都是基于他們的。
touch 相關(guān)事件touchstart 觸摸屏幕的瞬間
touchmove 手指在屏幕上的移動(dòng)過程一直觸發(fā)
touchend 離開屏幕的瞬間
touchcancel 觸摸取消(取決于瀏覽器實(shí)現(xiàn),并不常用)
觸摸屏下事件觸發(fā)順序是
touchstart -> touchmove -> touchend -> click引入 touch 的背景
click事件在移動(dòng)端上會(huì)有 300ms 的延遲,同時(shí)因?yàn)樾枰?長(zhǎng)按,雙觸擊 等富交互,所以我們通常都會(huì)引入類似 zepto 這樣的庫。zepto 實(shí)現(xiàn)了"swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap", "singleTap", "longTap" 這樣一些功能。
zepto touch 源碼我們直接看到 touch 源碼的 49 行,從這里開始就是上述事件的實(shí)現(xiàn)了。不難想到 MSGesture 是對(duì) mobile ie 的實(shí)現(xiàn),本文不做討論。往下面看到 66 行,$(document).on("touchstart MSPointerDown pointerdown") 開始。
//判斷事件類型是否為 touch if((_isPointerType = isPointerEventType(e, "down")) && !isPrimaryTouch(e)) return // touches 是觸摸點(diǎn)的數(shù)量 firstTouch = _isPointerType ? e : e.touches[0] if (e.touches && e.touches.length === 1 && touch.x2) { touch.x2 = undefined touch.y2 = undefined } // 記錄第一次觸摸的時(shí)間 now = Date.now() // 計(jì)算本次觸摸與最后一次的時(shí)間差 delta = now - (touch.last || now) // 查找 touch 事件的 dom touch.el = $("tagName" in firstTouch.target ? firstTouch.target : firstTouch.target.parentNode) // 如果 touchTimeout 存在就清理掉 touchTimeout && clearTimeout(touchTimeout) // 記錄當(dāng)前坐標(biāo) touch.x1 = firstTouch.pageX touch.y1 = firstTouch.pageY // 觸摸時(shí)間差小于 250ms 則為 DoubleTap if (delta > 0 && delta <= 250) touch.isDoubleTap = true // 記錄執(zhí)行后的時(shí)間 touch.last = now // 留一個(gè)長(zhǎng)觸摸,如果 touchmove 會(huì)把這個(gè)清理掉 longTapTimeout = setTimeout(longTap, longTapDelay)
接下來是 $(document).on("touchmove MSPointerMove pointermove")
//判斷事件類型是否為 move if((_isPointerType = isPointerEventType(e, "move")) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0] // 一旦進(jìn)入 move 就會(huì)清理掉 LongTap cancelLongTap() // 當(dāng)前手指坐標(biāo) touch.x2 = firstTouch.pageX touch.y2 = firstTouch.pageY // x 軸和 y 軸的變化量 Math.abs 是取絕對(duì)值的意思 deltaX += Math.abs(touch.x1 - touch.x2) deltaY += Math.abs(touch.y1 - touch.y2)
最后當(dāng)然就是 $(document).on("touchend MSPointerUp pointerup") 了,這個(gè)也是整個(gè) touch 最為復(fù)雜的一部分。
if((_isPointerType = isPointerEventType(e, "up")) && !isPrimaryTouch(e)) return cancelLongTap() // 如果是 swipe,x 軸或者 y 軸移動(dòng)超過 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 的時(shí)候方便清除 tapTimeout = setTimeout(function() { // 創(chuàng)建 tap 事件,并增加 cancelTouch 方法 var event = $.Event("tap") event.cancelTouch = cancelAll touch.el.trigger(event) // 觸發(fā) DoubleTap if (touch.isDoubleTap) { if (touch.el) touch.el.trigger("doubleTap") touch = {} } // singleTap (這個(gè)概念是相對(duì)于 DoubleTap 的,可以看看我們?cè)谧畛醯哪嵌卧创a解析中有這樣一段 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
整個(gè)讀下來其實(shí)就是對(duì) touchstart, touchmove, touchend 做了一些封裝和判斷,然后通過 zepto 自己的事件體系來注冊(cè)和觸發(fā)。
fastclick 對(duì)比 zepto我們?cè)诹牡揭苿?dòng)端 js 方案的時(shí)候很容易聽到這兩者,但我個(gè)人認(rèn)為這兩者是無法對(duì)比的。原因如下:zepto 是一個(gè)移動(dòng)端的 js 庫,而 fastclick 專注于 click 在移動(dòng)端的觸發(fā)問題。fastclick 的 github 主頁上有一句話是“Polyfill to remove click delays on browsers with touch UIs”,翻譯過來就是干掉移動(dòng)端 click 延時(shí)的補(bǔ)丁。這個(gè)延時(shí)就是我們?cè)谝?touch 的背景里提到過。
fastclick 源碼分析不愿意下代碼的可以直接點(diǎn)這里github地址首先贊一下 fastclick 的代碼注釋,非常全。
fastclick 的使用非常簡(jiǎn)單,直接 FastClick.attach(document.body); 一句話搞定。所以源碼分析就從 attach 方法來看吧,824 行
FastClick.attach = function(layer, options) { // 返回 FastClick 實(shí)例 layer 是一個(gè) element 通常是 document.body ,options 自然就是配置了 return new FastClick(layer, options); };
接下來回到 23 行看到 FastClick 構(gòu)造函數(shù),
// 方法綁定,兼容老版本的安卓 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 轉(zhuǎn)換為 addEventListener 的方式 if (typeof layer.onclick === "function") { oldOnClick = layer.onclick; layer.addEventListener("click", function(event) { oldOnClick(event); }, false); layer.onclick = null; }
FastClick.prototype.onTouchStart 和 zepto 一樣做了一些參數(shù)的紀(jì)錄,所以我這里就直接跳到 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 的時(shí)間間隔內(nèi) click 觸發(fā) if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { this.cancelNextClick = true; return true; } // 超出 longtap 的時(shí)間 if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) { return true; } this.cancelNextClick = false; // 紀(jì)錄當(dāng)前時(shí)間 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); // 觸發(fā) 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; } } // 最后就來觸發(fā) 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; // 拿觸摸的第一個(gè)手指 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; // 觸發(fā) clickEvent 事件 targetElement.dispatchEvent(clickEvent); };
到此 fastclick 主要的東西我們就看得差不多了,代碼當(dāng)中不難看到 fastclick 的兼容性做的很好。它的主要目的是解決 click 在觸摸屏下的使用,引入之后再初始化一次就好了,很適合復(fù)用代碼的情景。
擴(kuò)展講一下 touchEvent本文中 zepto 和 fastclick 都有用到 touchEvent,但是 zepto 當(dāng)中用的是 e.touches 而 fastclick 卻用的是 e.targetTouches。這兩者的差異我們來一點(diǎn)一點(diǎn)地扒。
TouchEvent 是一類描述手指在觸摸平面(觸摸屏、觸摸板等)的狀態(tài)變化的事件。這類事件用于描述一個(gè)或多個(gè)觸點(diǎn),使開發(fā)者可以檢測(cè)觸點(diǎn)的移動(dòng),觸點(diǎn)的增加和減少,等等。
屬性:
TouchEvent.changedTouches 一個(gè) TouchList 對(duì)象,包含了代表所有從上一次觸摸事件到此次事件過程中,狀態(tài)發(fā)生了改變的觸點(diǎn)的 Touch 對(duì)象。
TouchEvent.targetTouches 一個(gè) TouchList 對(duì)象,是包含了如下觸點(diǎn)的 Touch 對(duì)象:觸摸起始于當(dāng)前事件的目標(biāo) element 上,并且仍然沒有離開觸摸平面的觸點(diǎn).
TouchEvent.touches 一個(gè) TouchList 對(duì)象,包含了所有當(dāng)前接觸觸摸平面的觸點(diǎn)的 Touch 對(duì)象,無論它們的起始于哪個(gè) element 上,也無論它們狀態(tài)是否發(fā)生了變化。
TouchEvent.type 此次觸摸事件的類型,可能值為 touchstart, touchmove, touchend 等等
TouchEvent.target 觸摸事件的目標(biāo) element,這個(gè)目標(biāo)元素對(duì)應(yīng) TouchEvent.changedTouches 中的觸點(diǎn)的起始元素。
TouchEvent.altKey, TouchEvent.ctrlKey, TouchEvent.metaKey, TouchEvent.shiftKey 觸摸事件觸發(fā)時(shí),鍵盤對(duì)應(yīng)的鍵(例如 alt )是否被按下。
TouchList 與 TouchTouchList 就是一系列的 Touch,通過 TouchList.length 可以知道當(dāng)前有幾個(gè)觸點(diǎn),TouchList[0] 或者 TouchList.item(0) 用來訪問第一個(gè)觸點(diǎn)。
屬性
Touch.identifier:touch 的唯一標(biāo)志,整個(gè) touch 過程中(也就是 end 之前)不會(huì)改變
Touch.screenX 和 Touch.screenY:坐標(biāo)原點(diǎn)為屏幕左上角
Touch.clientX 和 Touch.clientY:坐標(biāo)原點(diǎn)在當(dāng)前可視區(qū)域左上角,這兩個(gè)值不包含滾動(dòng)偏移
Touch.pageX 和 Touch.pageY:坐標(biāo)原點(diǎn)在HTML文檔左上角,這兩個(gè)值包含了水平滾動(dòng)的偏移
Touch.radiusX 和 Touch.radiusY:觸摸平面的最小橢圓的水平軸(X軸)半徑和垂直軸(Y軸)半徑
Touch.rotationAngle:觸摸平面的最小橢圓與水平軸順時(shí)針夾角
Touch.force:壓力值 0.0-1.0
Touch.target:Touch相關(guān)事件觸發(fā)時(shí)的 element 不會(huì)隨 move 變化。如果 move 當(dāng)中該元素被刪掉,這個(gè) target 依然會(huì)不變,但不會(huì)冒泡。最佳實(shí)踐是將觸摸事件的監(jiān)聽器綁定到這個(gè)元素本身, 防止元素被移除后, 無法再從它的上一級(jí)元素上偵測(cè)到從該元素冒泡的事件。
希望本文能解答一些大家在移動(dòng)端開發(fā)當(dāng)中的一些問題,本文行文匆忙如有不正確的地方希望能回復(fù)告知。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/79895.html
摘要:在觸發(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)擊延遲的問題,同時(shí)也提供了滑動(dòng)的 swip...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個(gè)判斷需要引入設(shè)備偵測(cè)模塊。然后是監(jiān)測(cè)事件,根據(jù)這三個(gè)事件,可以組合出和事件。其中變量對(duì)象和模塊中的對(duì)象的作用差不多,可以先看看讀源碼之模塊對(duì)模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經(jīng)放到了github上,歡...
摘要:所有瀏覽器瀏覽器不支持安卓中中有屬性安卓中中有屬性有屬性的有屬性的所以在不需要的瀏覽器會(huì)直接掉,不會(huì)執(zhí)行下面的所有代碼。見源碼行,可以看出在響應(yīng)無操作后,則觸發(fā)。 其實(shí)一直就想花些時(shí)間讀一讀那些優(yōu)秀的開源庫,今天終于下了決定打算死磕下自己,2016年每個(gè)月讀2-3個(gè)優(yōu)秀的開源庫,把源碼精彩的地方和自己心得分享給大家。 目錄 (一)背景(二)源碼解析(三)Zepto 點(diǎn)擊穿透與 Fast...
摘要:分別存儲(chǔ)事件的定時(shí)器。事件定時(shí)器延時(shí)時(shí)間存儲(chǔ)事件對(duì)象滑動(dòng)方向判斷我們根據(jù)下圖以及對(duì)應(yīng)的代碼來理解滑動(dòng)的時(shí)候方向是如何判定的。取消長(zhǎng)按,以及取消所有事件取消長(zhǎng)按取消所有事件方式都是類似,先調(diào)用取消定時(shí)器,然后釋放對(duì)應(yīng)的變量,等候垃圾回收。 前言 移動(dòng)端原生支持touchstart、touchmove、touchend等事件,但是在平常業(yè)務(wù)中我們經(jīng)常需要使用swipe、tap、double...
閱讀 1808·2021-11-22 09:34
閱讀 3096·2019-08-30 15:55
閱讀 675·2019-08-30 15:53
閱讀 2061·2019-08-30 15:52
閱讀 3007·2019-08-29 18:32
閱讀 1996·2019-08-29 17:15
閱讀 2402·2019-08-29 13:14
閱讀 3564·2019-08-28 18:05