移動端click事件延遲300ms的原因以及解決辦法[轉載]

來源:互聯網
上載者:User

標籤:

 

原文:http://www.bubuko.com/infodetail-822565.html

這要追溯至 2007 年初。蘋果公司在發布首款 iPhone 前夕,遇到一個問題 —— 當時的網站都是為大螢幕裝置所設計的。於是蘋果的工程師們做了一些約定,應對 iPhone 這種小螢幕瀏覽案頭端網站的問題。這當中最出名的,當屬雙擊縮放(double tap to zoom)。這也是會有上述 300 毫秒延遲的主要原因。

當使用者一次點擊螢幕之後,瀏覽器並不能立刻判斷使用者是要進行雙擊縮放,還是想要進行單擊操作。因此,iOS Safari 就等待 300 毫秒,以判斷使用者是否再次點擊了螢幕。

於是,300 毫秒延遲就這麼誕生了。

FastClick 是 FT Labs 專門為解決移動端瀏覽器 300 毫秒點擊延遲問題所開發的一個輕量級的庫。簡而言之,FastClick 在檢測到 touchend 事件的時候,會通過 DOM 自訂事件立即觸發一個類比click 事件的click事件(自訂事件),並把瀏覽器在 300 毫秒之後真正觸發的 click 事件阻止掉。

FastClick 非常實際地解決 300 毫秒點擊延遲的問題。唯一的缺點可能也就是該指令碼的檔案尺寸 (儘管它不大)。如果你非常在意這點檔案大小,可以嘗試一下 Filament Group 的 Tappy!,或者 tap.js。兩者都相當輕量,能夠通過監聽 tap 而非 click 事件來繞過 300 毫秒延遲。

當然,zepto庫函數中,也有一個touch模組,此模組也包含tap事件,可以通過綁定tap來取代click事件。

但是zepto的tap事件會有點透問題。如何解決,請看下篇分解。

接下來,我們來詳細瞭解一個問題:FastClick解決延遲點擊的源碼解析。

FastClick解決延遲點擊的源碼解析。

首先,我們來看FastClick的使用。

window.addEventListener( "load", function() {    FastClick.attach( document.body );}, false );

這樣就解決了 300 毫秒點擊延遲的問題。

FastClick的源碼:

FastClick.attach = function(layer) {‘use strict‘;return new FastClick(layer);};

在FastClick的建構函式中,會有下面這段代碼:

this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };if (FastClick.notNeeded(layer)) {return;}if (this.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(‘touchend‘, this.onTouchEnd, false);layer.addEventListener(‘touchcancel‘, this.onTouchCancel, false);

也就是在document.body上綁定了click,touchstart,touchend,touchcancel事件。

這裡假設,我們的頁面有一個button,綁定了click事件。

當使用者點擊此button時,會先觸發touchstart事件,這時,會冒泡到document.body中,於是就會執行:

FastClick.prototype.onTouchStart = function(event) {‘use strict‘;var targetElement, touch, selection;if (event.targetTouches.length > 1) {return true;}targetElement = this.getTargetElementFromEventTarget(event.target);touch = event.targetTouches[0];if (this.deviceIsIOS) {selection = window.getSelection();if (selection.rangeCount && !selection.isCollapsed) {return true;}if (!this.deviceIsIOS4) {if (touch.identifier === this.lastTouchIdentifier) {event.preventDefault();return false;}this.lastTouchIdentifier = touch.identifier;this.updateScrollParent(targetElement);}}this.trackingClick = true;this.trackingClickStart = event.timeStamp;this.targetElement = targetElement;this.touchStartX = touch.pageX;this.touchStartY = touch.pageY;if ((event.timeStamp - this.lastClickTime) < 200) {event.preventDefault();}return true;};

這個回呼函數主要做了以下事情:

擷取我們當前觸發touchstart的元素,這裡是button。

然後將滑鼠的資訊記錄了下來,記錄滑鼠的資訊主要是為了在後面touchend觸發時,根據這裡得到的x、y判斷是否為click。

之後,會觸發touchend事件,然後冒泡到document.body上,執行以下代碼:

FastClick.prototype.onTouchEnd = function(event) {‘use strict‘;var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;if (this.touchHasMoved(event) || (event.timeStamp - this.trackingClickStart) > 300) {this.trackingClick = false;this.targetElement = null;}if (!this.trackingClick) {return true;}if ((event.timeStamp - this.lastClickTime) < 200) {this.cancelNextClick = true;return true;}this.lastClickTime = event.timeStamp;trackingClickStart = this.trackingClickStart;this.trackingClick = false;this.trackingClickStart = 0;if (this.deviceIsIOSWithBadTarget) {touch = event.changedTouches[0];targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset);}targetTagName = targetElement.tagName.toLowerCase();if (targetTagName === ‘label‘) {forElement = this.findControl(targetElement);if (forElement) {this.focus(targetElement);if (this.deviceIsAndroid) {return false;}targetElement = forElement;}} else if (this.needsFocus(targetElement)) {if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === ‘input‘)) {this.targetElement = null;return false;}this.focus(targetElement);if (!this.deviceIsIOS4 || targetTagName !== ‘select‘) {this.targetElement = null;event.preventDefault();}return false;}if (this.deviceIsIOS && !this.deviceIsIOS4) {scrollParent = targetElement.fastClickScrollParent;if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {return true;}}if (!this.needsClick(targetElement)) {event.preventDefault();this.sendClick(targetElement, event);}return false;};

注意上面的代碼中,event.preventDefault();會阻止真實的click事件的觸發,因此,在button上面的click事件不會觸發。

接下來,我們只需要查看sendClick方法。

FastClick.prototype.sendClick = function(targetElement, event) {‘use strict‘;var clickEvent, touch;if (document.activeElement && document.activeElement !== targetElement) {document.activeElement.blur();}touch = event.changedTouches[0];clickEvent = document.createEvent(‘MouseEvents‘);
      clickEvent.initMouseEvent(‘click‘, true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);clickEvent.forwardedTouchEvent = true;targetElement.dispatchEvent(clickEvent);};

在此方法中,會建立一個自訂的click事件,然後在button上立即觸發,於是,button綁定的click的事件回呼函數馬上執行,因此就沒有300ms延遲了。

上面的initMouseEvent方法的前三個參數的意思:1.事件類型,2.是否冒泡,3.是否阻止瀏覽器的預設行為。

自訂的click事件阻止了瀏覽器的預設行為,事件冒泡,於是執行document.body的click事件回呼函數。代碼如下:

FastClick.prototype.onClick = function(event) {‘use strict‘;var permitted;if (this.trackingClick) {this.targetElement = null;this.trackingClick = false;return true;}if (event.target.type === ‘submit‘ && event.detail === 0) {return true;}permitted = this.onMouse(event);if (!permitted) {this.targetElement = null;}return permitted;};

然后里面有一句 permitted = this.onMouse(event);於是,我們查看onMouse方法:

FastClick.prototype.onMouse = function(event) {‘use strict‘;if (!this.targetElement) {return true;}if (event.forwardedTouchEvent) {return true;}if (!event.cancelable) {return true;}if (!this.needsClick(this.targetElement) || this.cancelNextClick) {if (event.stopImmediatePropagation) {event.stopImmediatePropagation();} else {event.propagationStopped = true;}event.stopPropagation();event.preventDefault();return false;}return true;};

此方法,會阻止類比的click事件的冒泡以及預設行為。

到此,解決了移動端瀏覽器click事件延遲300ms的問題。

移動端click事件延遲300ms的原因以及解決辦法[轉載]

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.