在前端的移動Web開發中,有一部分事件只在移動端產生,如觸摸相關的事件。接下來給大家簡單總結一下移動端的事件。
1. PC端事件在移動端的兼容問題
1.1 click事件的200~300ms延遲問題
由於移動端默認的布局視口寬度是980像素,所以網頁文字非常小,為了快速讓網頁還原到原來的大小,Safari最新引入了雙擊縮放功能:用戶雙擊手機頁面的時候,瀏覽器會智能的縮放當前頁面到原始大小。
雙擊縮放的原理就是,當用戶click一次之后,瀏覽器會經過約300ms之后檢測是否再有一次click,如果有的話,就會縮放頁面。否則的話就是一個click事件。
由於雙擊縮放功能存在,click事件觸發就會有大約200~300ms的延遲。
1.2 dblclick事件失效
由於雙擊縮放的存在,pc端的dblclick事件也失效了。
2. 移動端特有的touch事件
由於移動端設備大都具備觸摸功能,所以移動端瀏覽器都引入了觸摸(touch)事件。
touch相關的事件跟普通的其他dom事件一樣使用,可以直接用addEventListener來監聽和處理。
最基本的touch事件包括4個事件:
-
touchstart: 當在屏幕上按下手指時觸發
-
touchmove: 當在屏幕上移動手指時觸發
-
touchend: 當在屏幕上抬起手指時觸發
-
touchcancel 當一些更高級別的事件發生的時候(如電話接入或者彈出信息)會取消當前的touch操作,即觸發touchcancel。一般會在touchcancel時暫停游戲、存檔等操作。
2.1 touch事件與click事件同時觸發
在很多情況下,觸摸事件和鼠標事件會同時被觸發(目的是讓沒有對觸摸設備優化的代碼仍然可以在觸摸設備上正常工作)。
因為雙擊縮放檢測的存在,在移動設備屏幕上點擊操作的事件執行順序:
touchstart(瞬間觸發) → touchend → click(200-300ms延遲)
如果你使用了觸摸事件,可以調用 event.preventDefault()來阻止鼠標事件被觸發。
2.2 touchstart事件
當用戶手指觸摸到的觸摸屏的時候觸發。事件對象的 target 就是touch 發生位置的那個元素。
<div> 點擊我! </div> <script> var box = document.querySelector("div"); box.addEventListener("touchstart", function (e) { console.log('touchstart'); }); </script>
2.3 touchmove事件
當用戶在觸摸屏上移動觸點(手指)的時候,觸發這個事件。一定是先要觸發touchstart事件,再有可能觸發 touchmove 事件。
touchmove 事件的target 與最先觸發的 touchstart 的 target 保持一致。touchmove事件和鼠標的mousemove事件一樣都會多次重復調用,所以,事件處理時不能有太多耗時操作。不同的設備,移動同樣的距離 touchmove 事件的觸發頻率是不同的。
注意:
- 即使手指移出了 原來的target 元素,則 touchmove 仍然會被一直觸發,而且 target 仍然是原來的 target 元素。
- touchmove事件會多次重復觸發,由於移動端計算資源寶貴,盡量保證事件節流
<div> <p></p> </div> <script> var i = 1; var box = document.querySelector("div"); var p = document.querySelector("p"); box.addEventListener("touchmove", function (e){ p.innerHTML = e.target.tagName + ", " + i++; }) </script>

2.4 touchend事件
當用戶的手指抬起的時候,會觸發 touchend 事件。如何用戶的手指從觸屏設備的邊緣移出了觸屏設備,也會觸發 touchend 事件。
touchend 事件的 target 也是與 touchstart 的 target 一致,即使已經移出了元素。

2.5 touchcancel事件
當觸點由於某些原因被中斷時觸發。有幾種可能的原因如下(具體的原因根據不同的設備和瀏覽器有所不同):
- 由於某個事件取消了觸摸:例如觸摸過程被一個模態的彈出框打斷。
- 觸點離開了文檔窗口,而進入了瀏覽器的界面元素、插件或者其他外部內容區域。
- 當用戶產生的觸點個數超過了設備支持的個數,從而導致 TouchList 中最早的 Touch對象被取消
touchcancel 事件一般用於保存現場數據。比如:正在玩游戲,如果發生了 。touchcancel 事件,則應該把游戲當前狀態相關的一些數據保存起來。
3. 觸摸事件對象
TouchEvent
是一類描述手指在觸摸平面(觸摸屏、觸摸板等)的狀態變化的事件。這類事件用於描述一個或多個觸點,使開發者可以檢測觸點的移動,觸點的增加和減少,等等。
每 個 Touch
對象代表一個觸點; 每個觸點都由其位置,大小,形狀,壓力大小,和目標 element
描述。 TouchList
對象代表多個觸點的一個列表.
3.1 TouchEvent
TouchEvent
的屬性繼承了 UIEvent
和 Event
。
屬性列表:
-
TouchEvent.changedTouches
: 一個TouchList
對象,包含了代表所有從上一次觸摸事件到此次事件過程中,狀態發生了改變的觸點的Touch
對象。 -
TouchEvent.targetTouches
: 一個TouchList
對象,是包含了如下觸點的Touch
對象:觸摸起始於當前事件的目標element
上,並且仍然沒有離開觸摸平面的觸點。 -
TouchEvent.touches
: 一 個TouchList
對象,包含了所有當前接觸觸摸平面的觸點的Touch
對象,無論它們的起始於哪個element
上,也無論它們狀態是否發生了變化。
<style> .box { width: 100px; height: 100px; border: 1px solid #09c; background-color: #0dc; } </style> <div class="box"></div> <script> window.onload = function() { var box = document.querySelector('.box'); box.addEventListener('touchstart', function(e) { console.dir(e); // 查看TouchEvent對象的屬性和方法 }); } </script>

3.2 TouchList詳解
一個TouchList
代表一個觸摸屏幕上所有觸點的列表。
舉例來講, 如果一個用戶用三根手指接觸屏幕(或者觸控板), 與之相關的TouchList
對於每根手指都會生成一個 Touch
對象, 共計 3 個.
-
只讀屬性:
length
返回這個
TouchList
中Touch
對的個數。(就是有幾個手指接觸到了屏幕) -
方法:
item(index)
返回
TouchList
中指定索引的Touch
對象。
<div> <p style="font-size: 50px; color: #ffffff;"></p> </div> <script> var box = document.querySelector("div"); var p = document.querySelector("p"); box.addEventListener("touchend", function (e){ p.innerHTML = e.changedTouches.length; //返回Touch對象的個數 for(var i = 0; i < e.changedTouches.length; i++){ //遍歷出來每個Touch對象 console.log(e.changedTouches.item(i)); } }) </script>

測試多個手機觸摸屏幕:
<div></div> <p></p> <script> var div = document.querySelector("div"); var p = document.querySelector("p"); div.addEventListener("touchstart", function (e){ var msg = "touches.length: " + e.touches.length + "<br> targetTouches.length: " + e.targetTouches.length + "<br> changedTouches.length: " + e.changedTouches.length; p.innerHTML = msg; }) </script>
操作:
-
放1個手指在div上
- 先放1個手指在其他地方,然后再放1個手指在
div
上
- 先放1個手指在其他地方,然后再逐漸放2個手指在
div
上
3.3 Touch詳解
Touch
表示用戶和觸摸設備之間接觸時單獨的交互點(a single point of contact
)。 這個交互點通常是一個手指或者觸摸筆, 觸摸設備通常是觸摸屏或者觸摸板。
基本屬性列表(都是只讀):
編號 | 屬性名 | 屬性說明 |
---|---|---|
1. | identifier |
表示每 1 個 Touch 對象 的獨一無二的 identifier 。有了這個 identifier 可以確保你總能追蹤到這個 Touch 對象。 |
2. | screenX |
觸摸點相對於屏幕左邊緣的 x 坐標。 |
3. | scre enY |
觸摸點相對於屏幕上邊緣的 y 坐標。 |
4. | clientX |
觸摸點相對於瀏覽器的 viewport 左邊緣的 x 坐標。不會包括左邊的滾動距離。 |
5. | clientY |
觸摸點相對於瀏覽器的 viewport 上邊緣的 y 坐標。不會包括上邊的滾動距離。 |
6. | pageX |
觸摸點相對於 document 的左邊緣的 x 坐標。 與 clientX 不同的是,他包括左邊滾動的距離,如果有的話。 |
7. | pageY |
觸摸點相對於 document 的左邊緣的 y 坐標。 與 clientY 不同的是,他包括上邊滾動的距離,如果有的話。 |
8. | target |
總是表示 手指最開始放在觸摸設備上的觸發點所在位置的 element 。 即使已經移出了元素甚至移出了document , 他表示的element 仍然不變 |
案例:
var box = document.querySelector("div"); var p = document.querySelector("p"); box.ontouchstart = function (e){ var touchList = e.changedTouches; for (var i = 0; i < touchList.length; i++){ var touch = touchList[i]; var msg = `id : ${touch.identifier} <br> screenX : ${touch.screenX} <br> screenY : ${touch.screenY} <br> clientX : ${touch.clientX} <br> clientY : ${touch.clientY} <br> pageX : ${touch.pageX} <br> pageY : ${touch.pageY} <br> target: ${touch.target.nodeName} <br> `; p.innerHTML = msg; } }
沒有左右滾動:

左右滾動:pageX
明顯大於 clientX

4. 封裝移動端tap事件
由於點擊事件經常使用,如果用click會有延遲問題,一般我們會用touch事件模擬移動端的點擊事件, 以下是封裝的幾個事件,僅供參考。
(function (window){ //傳入window,提高變量的查找效率 function myQuery(selector){ //這個函數就是對外提供的接口。 //調用這個函數的原型對象上的_init方法,並返回 return myQuery.prototype._init(selector); } myQuery.prototype = { /*初始化方法,獲取當前query對象的方法*/ _init: function (selector){ if (typeof selector == "string"){ //把查找到的元素存入到這個原型對象上。 this.ele = window.document.querySelector(selector); //返回值其實就是原型對象。 return this; } }, /*單擊事件: * 為了規避click的300ms的延遲,自定義一個單擊事件 * 觸發時間: * 當抬起手指的時候觸發 * 需要判斷手指落下和手指抬起的事件間隔,如果小於500ms表示單擊時間。 * 如果是大於等於500ms,算是長按時間 * */ tap: function (handler){ this.ele.addEventListener("touchstart", touchFn); this.ele.addEventListener("touchend", touchFn); var startTime, endTime; function touchFn(e){ e.preventDefault() switch (e.type){ case "touchstart": startTime = new Date().getTime(); break; case "touchend": endTime = new Date().getTime(); if (endTime - startTime < 500){ handler.call(this, e); } break; } } }, /** * 長按 * @param handler */ longTag: function (handler){ this.ele.addEventListener("touchstart", touchFn); this.ele.addEventListener("touchmove", touchFn); this.ele.addEventListener("touchend", touchFn); var timerId; function touchFn(e){ switch (e.type){ case "touchstart" : //500ms之后執行 timerId = setTimeout(function (){ handler.call(this, e); }, 500) break; case "touchmove" : //如果中間有移動也清除定時器 clearTimeout(timerId) break; case "touchend" : //如果在500ms之內抬起了手指,則需要定時器 clearTimeout(timerId); break; } } }, /** * 左側滑動。 * 記錄手指按下的左邊,在離開的時候計算 deltaX是否滿足左滑的條件 */ slideLeft: function (handler){ this.ele.addEventListener("touchstart", touchFn); this.ele.addEventListener("touchend", touchFn); var startX, startY, endX, endY; function touchFn(e){ e.preventDefault(); var firstTouch = e.changedTouches[0]; switch (e.type){ case "touchstart": startX = firstTouch.pageX; startY = firstTouch.pageY; break; case "touchend": endX = firstTouch.pageX; endY = firstTouch.pageY; //x方向移動大於y方向的移動,並且x方向的移動大於25個像素,表示在向左側滑動 if (Math.abs(endX - startX) >= Math.abs(endY - startY) && startX - endX >= 25){ handler.call(this, e); } break; } } }, /* 右側滑動 */ rightLeft: function (e){ //TODO: } } window.$ = window.myQuery = myQuery; })(window); // ======================== // 使用: $("div").tap(function (e){ console.log("單擊事件") }) $("div").longTag(function (){ console.log("長按事件"); }) $("div").slideLeft(function (e){ console.log(this); this.innerHTML = "左側滑動了....." })
5. 觸摸手勢封裝相關的框架及事件
手勢相關的事件一般就是tap類(觸屏)和滑動(swipe)事件兩類。都是基於原生的touchstart、touchmove、touchend事件,封裝成不同的手勢類型自定義事件。
5.1 tap類事件
觸碰事件,我目前還不知道它和touch的區別,一般用於代替click事件,有tap longTap singleTap doubleTap四種之分。
- tap: 手指碰一下屏幕會觸發
- longTap: 手指長按屏幕會觸發
- singleTap: 手指碰一下屏幕會觸發
- doubleTap: 手指雙擊屏幕會觸發
5.2 swipe類事件
滑動事件,有swipe swipeLeft swipeRight swipeUp swipeDown 五種之分。
- swipe:手指在屏幕上滑動時會觸發
- swipeLeft:手指在屏幕上向左滑動時會觸發
- swipeRight:手指在屏幕上向右滑動時會觸發
- swipeUp:手指在屏幕上向上滑動時會觸發
- swipeDown:手指在屏幕上向下滑動時會觸發

5.3 zepto的手勢相關事件
Zepto.js 是一個輕量級的針對現代高級瀏覽器的JavaScript庫, 它適配了jQuery的大部分api,也就是jQuery怎么用,Zepto.js就怎么用。它非常小,非常適合移動端。
Zepto.js的touch模塊中封裝了手勢相關的代碼。封裝了再觸摸設備上觸發tap– 和 swipe– 相關事件,也適用於所有的touch
(iOS, Android)和pointer
事件(Windows Phone)。
- 觸屏事件:tap、singleTap、doubleTap、longTap(>750ms)
- 滑動事件:swipe、swipeLeft,、swipeRight,、swipeUp,、swipeDown
<style>.delete { display: none; }</style> <ul id=items> <li>List item 1 <span class=delete>DELETE</span></li> <li>List item 2 <span class=delete>DELETE</span></li> </ul> <script> $('#items li').swipe(function(){ $('.delete').hide() $('.delete', this).show() }) $('.delete').tap(function(){ $(this).parent('li').remove() }) </script>
5.4 其他移動端手勢相關庫
-
hammer.js
hammer提供了不僅僅tap、swipe等事件,還提供了:pan(平移)、pinch類(捏拿縮放)、 press類(按住)、 rotate類(旋轉)類手勢支持, hammer.js詳解教程
6. 移動端點擊穿透問題
如果某個返回按鈕的位置,恰好在要返回的這個頁面的帶有href屬性的a標簽的范圍內,在點擊返回按鈕后,頁面快速切換到有a標簽的頁面,300ms后觸發了click事件,從而觸發了a標簽的意外跳轉,這個就是典型的點擊穿透問題。罪魁禍首其實就是a標簽跳轉默認是click事件觸發,而移動端的touch事件觸發之后,依然會在300ms后觸發click事件。
解決辦法:
1.就是阻止觸發touch事件完成后的click事件。
2.不要混用touch和click事件。顯然不可能都綁定click事件,因為要解決300ms延遲問題(除了fastclick),那么只能都綁定touch事件,這樣click事件永遠不會被觸發。
注意:zepto並沒有阻止click事件,所以使用zepto的tap事件依然會導致點擊穿透問題,你需要手動添加 e.preventDefault() 來阻止click事件。
參考文章: