點透 & 解決方案前端
學習map:node
再現點透現象請使用一下方式:android
touch screen
。代碼:ios
代碼:git
代碼:github
代碼:chrome
綜上,致使點透現象出現的場景:瀏覽器
PS:
a連接的href跳轉、input、select等表單元素的聚焦並彈起軟鍵盤,等觸發的事件和click同樣,因爲歷史緣由在手機端也會表現出300ms延遲,所以也均可以看作是click事件,我叫他類click事件,demo可用爪機狠戳事件觸發順序&click延遲demo或者複製連接到連圖生成二維碼後掃一掃。類click事件,也是一種瀏覽器默認行爲,可被event.preventDefault()阻止。iphone
代碼:函數
從事件觸發順序&click延遲demo這個demo能夠看出:
android
仍是ios
仍是PC的touch screen
的click(or mouse)事件都是遲於touchend
事件被觸發的touchstart
、touchend
、click
因爲咱們在touchend
階段z軸層級已經發生了變化,當click被觸發時候,可以被點擊的元素則是當前z軸離用戶最近的層,根據click事件的觸發規則:
在被觸發時,當前有綁定click事件的元素顯示,且在面朝用戶的最前端時,才觸發click事件
所以touchend以後符合條件的綁定了click事件的元素被點透。
總而言之:出現點透是因爲移動端click事件遲於touch事件被觸發致使的。
touchend + preventDefault
及時取消touch元素的默認click事件,即if(eve == "touchend") e.preventDefault();
。
若是犧牲點性能無所謂的話,能夠將可能在z軸方向上引發點透現象的元素綁定成click事件,好比遮罩層之類的,不過還能夠增長些許有趣的交互抵消用戶的焦躁心理,好比:demo-ripple。
統一使用touch事件。z軸上都綁定touchstart
和 touchend
、 tap
不用阻止默認行爲也不會穿透。
使用上述方法有很明顯的缺點和不方便:
使用touchend + preventDefault
要在同一個元素上綁定2個事件,zepto能夠封裝成tap事件,咱們也能夠,自定義tap事件阻止點透
代碼:
在本demo中,雖然沒有出現點透現象,可是點擊出現彈層之後你會發現點擊a連接、span、input都沒有任何反映了,這是由於在touchend裏阻止瀏覽器默認行爲,觸發自定義tap事件,不只會阻止掉了input的軟鍵盤彈出,還會阻止一切非tap事件,解決辦法就是使用合成的click事件去覆蓋會延遲的click事件。
代碼:
event.initEvent('click', bubbles, true); touch_target.addEventListener("click", handle, false);
這樣再次點擊,彈層上的a連接和span的click事件都響應地很迅速,然而input和select的彈出軟鍵盤的功能被閹割了,實際上input彈出軟鍵盤的事件是focus()事件
讀fastclick源碼
layer.removeEventListener('touchend', this.onTouchEnd, false); FastClick.prototype.onTouchEnd = function(event) { ... targetTagName = targetElement.tagName.toLowerCase(); 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; } this.focus(targetElement); this.sendClick(targetElement, event); if (!deviceIsIOS || targetTagName !== 'select') { this.targetElement = null; event.preventDefault(); } return false; } // needsFocus FastClick.prototype.needsFocus = function(target) { switch (target.nodeName.toLowerCase()) { case 'textarea': return true; case 'select': return !deviceIsAndroid; case 'input': switch (target.type) { case 'button': case 'checkbox': case 'file': case 'image': case 'radio': case 'submit': return false; } // No point in attempting to focus disabled inputs return !target.disabled && !target.readOnly; default: return (/\bneedsfocus\b/).test(target.className); } }; // sendClick 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]; // 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.dispatchEvent(clickEvent); };
上面代碼的意思就是:在目標元素
上綁定touchend事件,在事件處理函數裏,若是是須要focus的表單元素被點擊,則先觸發他們的focus事件,再觸發自定義的click事件,fastclick之因此大,就是由於對不少表單元素在各個系統的各個版本的不一樣表現作了兼容,不只解決了click以及類click的延遲問題,並且當檢測到當前頁面使用了基於 <meta>
標籤或者 touch-action
屬性的解決方案時,會靜默退出。能夠說,這是真正的跨平臺方案出來以前一種很好的變通方案。而zepto
只是爲普通的點擊事件封裝了一個更快的tap事件,類click事件的延遲問題並無獲得解決,並且移動端使用的tap事件,若是沒作設備判斷兼容PC的話,PC端的點擊事件將得不到響應,這會很影響網站的可用性和可訪問性。不過zepto封裝了一系列移動端很須要的功能,好比swipeLeft、swipeRight、swipeUp、等等,兩者各有春秋,兼併二者優點的庫我目前沒遇到,不過能夠嘗試本身寫一個,加個todo吧。
tap.js源碼只有不到200行,大體看了下,並不能解決類click的延遲問題,雞肋!