摘要:移動端觸摸點擊事件優化源碼學習最近在做一些微信移動端的頁面,在此記錄關于移動端觸摸和點擊事件的學習優化過程,主要內容圍繞展開。當指針設備通常指鼠標在元素上移動時事件被觸發。移動端有延遲問題,可沒有哦。
移動端觸摸、點擊事件優化(fastclick源碼學習)
最近在做一些微信移動端的頁面,在此記錄關于移動端觸摸和點擊事件的學習優化過程,主要內容圍繞fastclick展開。
fastclick github
移動端瀏覽器一般在用戶點擊屏幕之后會延遲大約300ms才觸發click event
手機打開此鏈接查看延遲demo
(現在許多瀏覽器已經不存在延遲問題了,詳見fastclick github,但筆者的手機瀏覽器還是出現了三百毫秒延遲的問題)
截圖如下
為什么會300ms延遲呢,主要是有一個雙擊縮放功能,瀏覽器需要判斷用戶點擊是否為雙擊縮放。這個問題不解決,
1、用戶體驗就會很差,很不流暢,尤其是在密集操作場景下,比如計算器,不解決300ms延遲問題,感覺反應很慢;
2、點擊穿透問題
在了解fastclick的思路之前,我們先看一下事件觸發順序是怎樣的
touchstart
touchmove
touchend
mouseover :當指針設備移動到存在監聽器的元素或其子元素的時候,mouseover事件就會被觸發。
mouseenter:當指針設備( 通常指鼠標 )在元素上移動時, mousemove 事件被觸發。
mousedown
click
移動端click有300ms延遲問題,touch可沒有哦。
fastclick思路fastclick的思路就是利用touch來模擬tap(觸碰),如果認為是一次有效的tap,則在touchend時立即模擬一個click事件,分發到事件源(相當于主動觸發一次click),同時阻止掉瀏覽器300ms后產生的click。
源碼學習先看使用示例,很簡單,我們的思路就一直跟著attach走。
if ("addEventListener" in document) { document.addEventListener("DOMContentLoaded", function() { FastClick.attach(document.body); }, false); }
直接給body綁定fastlick就行了- -。
看源代碼結構(注:以下所有代碼均去掉了一些不影響理解思路的部分,大部分思路寫在注釋中)
//構造函數 function FastClick(layer, options) //判斷是否需要瀏覽器原生的click事件(針對一些特殊元素比如表單) FastClick.prototype.needsClick = function(target) //發送模擬的click event FastClick.prototype.sendClick = function(targetElement, event) // touchstart eventhandler FastClick.prototype.onTouchStart = function(event) // touchmove eventhandler FastClick.prototype.onTouchMove = function(event) // touchend eventhandler FastClick.prototype.onTouchEnd = function(event) // 判斷這次tap是否有效 FastClick.prototype.onMouse = function(event) //click handler 捕獲階段監聽 FastClick.prototype.onClick = function(event) //銷毀fastlick,移除事件綁定 FastClick.prototype.destroy = function() //綁定接口 FastClick.attach = function(layer, options) { return new FastClick(layer, options); };
attach實際就執行了構造函數進行初始化,接下來我們來看構造函數發生了什么
function FastClick(layer,options){ //一些屬性初始化 //安卓一些老版本瀏覽器不支持bind, poly fill function bind (method, context) { return function () { return method.apply(context, arguments); }; } var methods = ["onMouse", "onClick", "onTouchStart", "onTouchMove", "onTouchEnd", "onTouchCancel"]; var context = this; //將所有handler的this綁定到fastclick實例 for (var i = 0, l = methods.length; i < l; i++) { context[methods[i]] = bind(context[methods[i]], context); } //為當前fast click對象綁定的layer(我們的示例中時document.body)加監聽 layer.addEventListener("click", this.onClick, true);//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); }
構造函數主要是初始化一些屬性,polyfill,和添加監聽,
下面開始看一下重頭戲,touchstart,touchend是如何判斷tap是否有效、如何模擬click事件、如何阻止300ms后的click
touchstart
FastClick.prototype.onTouchStart = function (event) { var targetElement, touch, selection; // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111). // 如果多觸點可能是在縮放,不對targetElement初始化,在此提前終止避免誤模擬產生click if (event.targetTouches.length > 1) { return true; } //獲取發生事件源元素(目標階段的元素) targetElement = this.getTargetElementFromEventTarget(event.target); touch = event.targetTouches[0]; this.trackingClick = true;//標記開始跟蹤click this.trackingClickStart = event.timeStamp;//開始跟蹤時間 this.targetElement = targetElement;//事件源元素 //觸摸坐標,接下來判斷是否越界用到 this.touchStartX = touch.pageX; this.touchStartY = touch.pageY; // Prevent phantom clicks on fast double-tap (issue #36) if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { event.preventDefault();//阻止之后的click } return true; };
touchstart主要是初始化跟蹤的tap相關的一些屬性,用于之后的判斷‘
接下來touchmove
FastClick.prototype.onTouchMove = function (event) { if (!this.trackingClick) { return true; } // If the touch has moved, cancel the click tracking 移動到了其他元素 if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {//移動越界了,取消本次click模擬處理,走原生流程 this.trackingClick = false; this.targetElement = null; } return true; };
touchmove比較簡單,主要是兼容滑動tap(swiper)等等,滑動越界則不模擬click
下面是touchend
FastClick.prototype.onTouchEnd = function (event) { var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement; if (!this.trackingClick) { return true; } // Prevent phantom clicks on fast double-tap (issue #36) //阻止快速雙擊 if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { this.cancelNextClick = true; return true; } //超時就不算click了,走原生流程,不阻止click if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) { return true; } this.lastClickTime = event.timeStamp; this.trackingClick = false; this.trackingClickStart = 0; // Prevent the actual click from going though - unless the target node is marked as requiring // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted. if (!this.needsClick(targetElement)) { event.preventDefault();//阻止之后的click this.sendClick(targetElement, event);//發送模擬click } return false; }; //發送模擬的click event FastClick.prototype.sendClick = function (targetElement, event) { var clickEvent, touch; // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24) if (document.activeElement && document.activeElement !== targetElement) { document.activeElement.blur(); } touch = event.changedTouches[0]; //模擬click // Synthesise a click event, with an extra attribute so it can be tracked 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; //向targetElement分發模擬的click targetElement.dispatchEvent(clickEvent); };
最后,還會在layer的click捕獲階段監聽
//click handler 捕獲階段監聽 FastClick.prototype.onClick = function (event) { var permitted; // It"s possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early. if (this.trackingClick) {//1、出界會置為false,2成功模擬了一次完成tap并阻止click也會置為false,3、避免三方庫影響 this.targetElement = null; this.trackingClick = false; return true; } // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of "fake" click event will be triggered with the submit-type input element as the target. if (event.target.type === "submit" && event.detail === 0) { return true; } permitted = this.onMouse(event); // Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser"s click doesn"t go through. if (!permitted) { this.targetElement = null; } // If clicks are permitted, return true for the action to go through. return permitted; }; // 判斷這次鼠標是否有效 FastClick.prototype.onMouse = function (event) { // If a target element was never set (because a touch event was never fired) allow the event if (!this.targetElement) { return true; } // 標記fastclick模擬產生的event if (event.forwardedTouchEvent) { return true; } // Programmatically generated events targeting a specific element should be permitted if (!event.cancelable) { return true; } // Derive and check the target element to see whether the mouse event needs to be permitted; // unless explicitly enabled, prevent non-touch click events from triggering actions, // to prevent ghost/doubleclicks. // 是否需要原生的click if (!this.needsClick(this.targetElement) || this.cancelNextClick) { // Prevent any user-added listeners declared on FastClick element from being fired. if (event.stopImmediatePropagation) { event.stopImmediatePropagation(); } else { // Part of the hack for browsers that don"t support Event#stopImmediatePropagation (e.g. Android 2) event.propagationStopped = true; } // Cancel the event 阻止事件捕獲和冒泡 event.stopPropagation(); event.preventDefault(); return false; } // If the mouse event is permitted, return true for the action to go through. return true; };
這里主要是判斷這次click是否有效(如無效,則阻止捕獲和冒泡)
至此基本流程已經結束。
其中有1個注意的點,筆者在chrome(Version 64.0.3282.119 (Official Build) (64-bit))已測試
stopPropagation,stopImmediatePropagation不僅會阻止冒泡還會阻止捕獲過程哦。
推薦閱讀源碼,源碼中有許多關于focus、不同瀏覽器兼容和特殊表單元素的處理fastclick github。
這里是筆者帶有中文注釋的代碼中文注釋代碼。
如有紕漏,歡迎批評指正。
MDN
https://juejin.im/entry/55d73...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107935.html
摘要:移動端觸摸點擊事件優化源碼學習最近在做一些微信移動端的頁面,在此記錄關于移動端觸摸和點擊事件的學習優化過程,主要內容圍繞展開。當指針設備通常指鼠標在元素上移動時事件被觸發。移動端有延遲問題,可沒有哦。 移動端觸摸、點擊事件優化(fastclick源碼學習) 最近在做一些微信移動端的頁面,在此記錄關于移動端觸摸和點擊事件的學習優化過程,主要內容圍繞fastclick展開。fastclic...
摘要:難道是安卓上和執行順序異于其他瀏覽器。因為使用了以后事件變得極其敏感,所有的事件觸發之前,都會觸發。按照的邏輯,一旦觸發之后,所有的都被阻止冒泡,就會出現上面說的問題,解決方案如下圖增加上圖這個判定的即可。 這兩天做H5頁面,使用swiper+iscroll+fastClick,并沒有用swiper提供的tap和click事件,自己在元素上bind,因為回調函數是統一處理,就沒用swi...
摘要:源碼分析不愿意下代碼的可以直接點這里地址首先贊一下的代碼注釋,非常全。屬性一個對象,包含了代表所有從上一次觸摸事件到此次事件過程中,狀態發生了改變的觸點的對象。 所謂 zepto 的 touch 其實就是指這個文件啦,可以看到區區 165 行(包括注釋)就完成了 swipe 和 tap 相關的事件實現。在正式開始分析源碼之前,我們先說說 touch 相關的幾個事件,因為無論是 tap ...
閱讀 3284·2021-11-24 09:39
閱讀 3865·2021-11-22 09:34
閱讀 4799·2021-08-11 11:17
閱讀 1060·2019-08-29 13:58
閱讀 2570·2019-08-28 18:18
閱讀 537·2019-08-26 12:24
閱讀 825·2019-08-26 12:14
閱讀 727·2019-08-26 11:58