移動端在touch上一共有4個事件
touchstart touchmove touchend touchcancel, touchcancel, 一般來說,它們執行的順序為 touchstart -> touchmove -> touchend -> touchcancel . 其中touchcancel一般情況下不會觸發,也不是這里討論的焦點;
這里會結合click對上面的事件進行討論, touch發生在click之前
先上段代碼,直觀感受一下
<!DOCTYPE html> <html> <head> <style type="text/css"> #level0 { /* width: 500px; height: 500px; */ } #level1-0 { background: red; width: 500px; height: 500px; } #level1-1 { background: green; width: 500px; height: 500px; } </style> </head> <body> <div id="level0"> <div id="level1-0"> </div> <div id="level1-1"> </div> </div> </body> <script type="text/javascript"> var level10 = document.getElementById("level1-0"); level10.addEventListener('touchstart', function(e) { console.log(1); }); level10.addEventListener('touchmove', function(e) { console.log(2); }); level10.addEventListener('touchend', function(e) { console.log(3); }); level10.onclick = function() { console.log(5); } document.body.onclick = function() { console.log('6'); } </script> </html>
在紅色區域點擊會出現什么效果呢? 出現的是 1 3 5 6, 奇怪了 touchmove 為何不執行,因為我們並沒有移動,也就是說,必須觸碰到屏幕上面,而且發生了移動動作,touchmove才執行,現在我們觸碰到,而且手指稍微動一下,發現輸出的效果是, 1 2(+) 3, 其中touchmove 可能觸發多次,又奇怪了, click為何不執行, 因為 click執行的條件是 點擊, 而且不移動 所以一般情況下,我們可以理解成 touchmove和click是相斥的。
我們知道,當一個用戶在點擊屏幕的時候,系統會觸發touch事件和click事件,touch事件優先處理,touch事件經過 捕獲,處理, 冒泡 一系列流程處理完成后, 才回去觸發click事件
既然touch事件和click事件有了優先級別,那么能不能在touch階段取消掉系統觸發的click事件呢?當然是可以的,瀏覽器提供了這樣的能力。在touch事件里面,調用e.preventDefault() 就可以阻止本次點擊系統觸發的click事件,即本次相關的click都不會執行
把上面代碼稍微加一點
level10.addEventListener('touchstart', function(e) { console.log(1); e.preventDefault(); });
點擊的時候 發現 只有 1 3, 說明click被阻止了,當然在touchend里面加效果也一樣,所以 在touch事件里面加 e.preventDefault可以取消系統產生的click事件, 當然不會阻止后面的touch事件。
用個具體的例子看看 如何解決點透問題
產生點透問題的原因, 可以先看看代碼吧
<!DOCTYPE html> <html> <head> <style type="text/css"> #level0 { /* width: 500px; height: 500px; */ position: relative; } #level1-0 { position: absolute; z-index: 1; background: red; width: 500px; height: 500px; } #level1-1 { background: green; width: 500px; height: 500px; } </style> </head> <body> <div id="level0"> <div id="level1-0"> </div> <div id="level1-1"> </div> </div> </body> <script type="text/javascript"> var level10 = document.getElementById("level1-0"); var level11 = document.getElementById("level1-1"); level10.addEventListener('touchstart', function(e) { level10.style.display = 'none'; }); level11.onclick = function() { console.log('level11莫名被點擊了'); } </script </html>
本來是 level1-0 和 level1-1是兄弟節點,即他們之間不會發生什么 事件傳遞, 目前level1-0相當於一個覆蓋層,覆蓋在level1-1上面, 按理說點擊 level1-0的時候,level1-0會阻擋所有的事件,事件不會傳遞給level1-1,當點擊level1-0的時候,實際上level1-1也發生了點擊事件,即上面的輸出結果為level1-0消失, 輸出
level11莫名被點擊了
, 這就是點透
點透發生的條件:
- A 和 B不是后代繼承關系(如果是后代繼承關系的話,就直接是冒泡子類的話題了)
- A發生touch, A touch后立即消失, B事件綁定click
- A z-index大於B,即A顯示在B浮層之上
點透發生的理由:
當手指觸摸到屏幕的時候,系統生成兩個事件,一個是touch 一個是click,touch先執行,touch執行完成后,A從文檔樹上面消失了,而且由於移動端click還有延遲200-300ms的關系,當系統要觸發click的時候,發現在用戶點擊的位置上面,目前離用戶最近的元素是B,所以就直接把click事件作用在B元素上面了
.
那如何才能解決點透問題呢? 還記得我之前說過么,系統提供了先觸發的touch事件去取消系統生成的click事件,所以只要在touch事件的某個處理函數中 執行 e.preverDefault即可, 一般我們在touchend中執行
在上面代碼中,加上這句就完美解決了
level10.addEventListener('touchend', function(e) {
e.preventDefault();
});
當然點透問題,還有其他的解決方法,關鍵是 要么是需求本次系統生成的click事件,要么是當系統觸發click的時候,當前的觸發touch的那個dom節點還存在。比如將其一延遲3s在關閉
setTimeout(() => { level10.style.display = 'none'; }, 300);