移動端時代已經到來,做爲前端開發的咱們沒有理由也不該該坐井觀天,而是勇敢地跳出內心的那口井,去擁抱蔚藍的天空。該來的總會來,咱們要作的就是接受未知的挑戰。正如你所看到的,這是一篇關於移動端觸摸事件的文章,也就是咱們平時在手機中用得最多的動做:touch。如今讓咱們開始 touch touch touch 吧!html
首先 touch 包含三類事件,它們分別是:touchstart、touchmove、touchend 。望文生義這種本能相信你應該會有,但在這裏我仍是有必需對這三個詞進行一翻沒必要要的解釋。前端
授課時間
touchstart:手指觸摸到一個 DOM 元素時觸發。瀏覽器
touchmove:手指在一個 DOM 元素上滑動時觸發。ide
touchend:手指從一個 DOM 元素上移開時觸發。測試
這三個事件又分別對應三個相同的觸摸列表:scala
授課時間
touches:正在觸摸屏幕的全部手指的一個列表。code
targetTouches:正在觸摸當前 DOM 元素上的手指的一個列表。htm
changedTouches:涉及當前事件的手指的一個列表。對象
事件對應的三個列表雖然名字不同,可是它們裏面裝的東西都是差很少的,包含了當前事件的一些相關信息,好比:一些座標信息。事件
TouchList {0: Touch, length: 1} length:1 0:Touch clientX:65 // 觸摸點在瀏覽器窗口中的橫座標 clientY:18 // 觸摸點在瀏覽器窗口中的縱座標 force:1 // 觸摸點壓力大小 identifier:0 // 觸摸點惟一標識(ID) pageX:65 // 觸摸點在頁面中的橫座標 pageY:18 // 觸摸點在頁面中的縱座標 radiusX:11.5 // 觸摸點橢圓的水平半徑 radiusY:11.5 // 觸摸點橢圓的垂直半徑 rotationAngle:0 // 旋轉角度 screenX:560 // 觸摸點在屏幕中的橫座標 screenY:175 // 觸摸點在屏幕中的縱座標 target:div#touchLog 觸摸目標 __proto__:Touch __proto__:TouchList
上面就是一個 TouchList 列表。它對應的就是前面提到的三種事件(touchstart、touchmove、touchend)中的一種,在觸發時生成的一個對象列表。列表裏最有用的就是 Touch 對象了,Touch 對象裏存放着對應事件的一些相關的信息,咱們就是經過這種個事件裏這些屬性的有機結合來實現各類效果。
經過上面的 radiusX,radiusY,rotationAngle 這三個屬性就能夠計算出你的手指觸摸手機屏幕時的一個接觸面,只不過這個接觸面是用一個近似的橢圓來表示,也就是說它不是一個真正意義上的接觸面,而是一個大概的接觸面。相信心細的朋友應該會看到 TouchList 對象裏有一個 length 屬性,而且它的值爲 1 ,這說明當前只有一個手指觸發了事件(好比:touchstart 事件),換句話說,此時你只有一個手指放到了手機屏幕上,這個手指對應的一些信息存放在 Touch 對象裏。由於只有一個手指放在了屏幕上,因此這個 TouchList 裏只有一個 Touch 對象,而且是第一個下標爲 0 。TouchList 列表裏還有一個 target 屬性,這個應該很好理解,就是觸摸的目標。
爲了讓你能更加立體地理解上面的這些屬性,我專門從網上找了一段話來做爲補充:
來自 mozilla
1.Touch.identifier:此 Touch 對象的惟一標識符。 一次觸摸動做(咱們指的是手指的觸摸)在平面上移動的整個過程當中,該標識符不變。 能夠根據它來判斷跟蹤的是不是同一次觸摸過程,此值爲只讀屬性。
2.Touch.screenX:觸點相對於屏幕左邊沿的X座標。只讀屬性。
3.Touch.screenY:觸點相對於屏幕上邊沿的Y座標。只讀屬性。
4.Touch.clientX:觸點相對於可見視區(visual viewport)左邊沿的X座標。不包括任何滾動偏移。只讀屬性。
5.Touch.clientY:觸點相對於可見視區(visual viewport)上邊沿的Y座標。不包括任何滾動偏移。只讀屬性。
6.Touch.pageX:觸點相對於HTML文檔左邊沿的X座標。當存在水平滾動的偏移時,這個值包含了水平滾動的偏移。只讀屬性。
7.Touch.pageY:觸點相對於HTML文檔上邊沿的Y座標。當存在水平滾動的偏移時,這個值包含了垂直滾動的偏移。只讀屬性。
8.Touch.radiusX:可以包圍用戶和觸摸平面的接觸面的最小橢圓的水平軸(X軸)半徑。這個值的單位和 screenX 相同。只讀屬性。
9.Touch.radiusY:可以包圍用戶和觸摸平面的接觸面的最小橢圓的垂直軸(Y軸)半徑。這個值的單位和 screenY 相同。只讀屬性。
10.Touch.rotationAngle:它是這樣一個角度值:由radiusX 和 radiusY描述的正方向的橢圓,須要經過順時針旋轉這個角度值,才能最精確地覆蓋住用戶和觸摸平面的接觸面。只讀屬性。
11.Touch.force:手指擠壓觸摸平面的壓力大小,從0.0(沒有壓力)到1.0(最大壓力)的浮點數。只讀屬性。
12.Touch.target:當這個觸點最開始被跟蹤時(在 touchstart 事件中),觸點位於的HTML元素。哪怕在觸點移動過程當中,觸點的位置已經離開了這個元素的有效交互區域,或者這個元素已經被從文檔中移除。須要注意的是,若是這個元素在觸摸過程當中被移除,這個事件仍然會指向它,可是不會再冒泡這個事件到 window 或 document 對象。所以,若是有元素在觸摸過程當中可能被移除,最佳實踐是將觸摸事件的監聽器綁定到這個元素自己,防止元素被移除後,沒法再從它的上一級元素上偵測到從該元素冒泡的事件。只讀屬性。
爲了更深刻地理解 Touch 事件,咱們如今來作一個簡單的 DEMO 。
HTML 代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>移動端觸摸(touch)事件</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <style> #touchLog { padding: 12px; width: 100%; box-sizing: border-box; height: 300px; } </style> </head> <body> <div id="touchLog">這裏顯示 touch 信息</div> </body> </html>
JavaScript 代碼
<script> var obj = document.getElementById("touchLog"); obj.addEventListener("touchstart", showMsg); function showMsg(ev) { console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } </script>
上面綁定的是 touchstart 事件,其它兩個事件的用法同樣,只是觸發的時間點不同而已。運行上面的代碼會輸出以下圖的結果:
可能你會問:爲何它們的數據都是同樣的,其實也很好理解,當你按下屏幕觸發 touchstart 此時,而後執行了showMsg() 方法。那改爲 touchmove 呢?不出意外的話,當你移動手指時第一組中的這三個 TouchList 也是同樣的,爲何說第一組呢,由於 touchmove 是在你移動手指時纔會觸發的,因此當你不斷移動手指時會屢次觸發天然而然地就會出現多組數據。但對於 touchend 就有點不同了。你能夠運行以下代碼查看 touchend 與其它兩個的區別:
<script> var touchLog = document.getElementById("touchLog"); touchLog.addEventListener("touchstart", showMsgTouchstart); function showMsgTouchstart(ev) { console.log("---touchstart---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } touchLog.addEventListener("touchmove", showMsgTouchmove); function showMsgTouchmove(ev) { console.log("---touchmove---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } touchLog.addEventListener("touchend", showMsgTouchend); function showMsgTouchend(ev) { console.log("---touchend---"); console.log(ev.touches); console.log(ev.targetTouches); console.log(ev.changedTouches); } </script>
運行後你會發現 touchend 的 touches、targetTouches 裏是沒有 touch 對象的,length 值都爲零,以下圖:
這裏咱們還能夠作別一個更有意思的測試,那就是觸摸目標元素後,手指再滑出目標元素。上面三個事件對應的 touches、targetTouches 和 changedTouches 又會輸出什麼呢?還會是同樣?
若是不出意外
1.touchstart 事件:touches、targetTouches 和 changedTouches 是同樣的。
2.touchmove 事件:touches、targetTouches 和 changedTouches 是同樣的(數據依然會有多組,緣由上面也已經分析過了)。
3.touchend 事件:當你手指離開屏幕後,也就是 touchend 事件觸發時,touches、targetTouches 的 TouchList 的 length 一樣爲0,也就是說沒有 touch 對象。
因此使用 touchend 事件時須要注意只有 changedTouches 會存有觸摸對象。但在 TouchList 對象中的 target 屬性的值都爲 div#touchLog,也就是說無論你觸摸屏幕後手指是在目標元素上仍是滑出目標元素,這個 target 屬性的值仍是 div#touchLog 。只要觸摸了屏幕 force 的值都是 1 ,因此在這裏感受這個 force 尚未什麼用武之地。
咱們先來看看下面的這一段代碼,而且若是條件容許的話請用手機訪問:http://yunkus.com/demo/mobile-touch-event/multi-finger-touchstart.html 查看touchstart 事件觸發時的效果,代碼以下:
<script> var obj = document.getElementById("touchLog"); obj.addEventListener("touchstart", showMsg); function showMsg(ev) { obj.innerHTML = ""; var touchesStr = ""; var targetTouchesStr = ""; for (var i = 0; i < ev.touches.length; i++) { touchesStr += "identifier:" + ev.touches[i].identifier + ",x 軸座標:" + ev.touches[i].clientX + "<br>"; targetTouchesStr += "identifier:" + ev.targetTouches[i].identifier + ",x 軸座標:" + ev.targetTouches[i] .clientX + "<br>"; } obj.innerHTML = "下面是 ev.touches 數據:<br>" + touchesStr + "<br>下面是 ev.targetTouches 數據:<br>" + targetTouchesStr + "<br>下面是 ev.changedTouches 數據:<br>" + "identifier:" + ev.changedTouches[0].identifier + ",x 軸座標:" + ev.changedTouches[0].clientX + "<br>"; } </script>
訪問 http://yunkus.com/demo/mobile-touch-event/multi-finger-touchmove.html 查看 touchmove 事件觸發時的效果,touchmove 事件觸發後 changedTouches 裏的 touch 對象就不僅一個了,而是跟其它兩個 TouchList 列表同樣。
訪問 http://yunkus.com/demo/mobile-touch-event/multi-finger-touchend.html 查看 touchend 事件觸發時的效果,touchmove 事件觸發後就只有 changedTouches 裏有 touch 且只有一個 touch 對象了,無論你同時在屏幕上放了多少根手指,這個 touch 對象對應的是你最後一次離開屏幕的那根手指。
上面的三個 demo 都會輸出事件觸發時的 touches 、targetTouches 和 changedTouches 裏的 identifier 值,以及一個clientX 值。clientX 用於讓你經過 x 的座標來判斷哪根手指對應哪一個 identifier 的。對於 touchstart 事件而言,由於 changedTouches 里老是保存一個 touch 對象,因此沒有遍歷,而是直接經過下標訪問。從上面咱們能夠得知有 touchstart 事件中 touches 、targetTouches 和 changedTouches 是有區別的:changedTouches 下只有一個 touch 對象,這個對象對應着觸發事件最後一根發生改變(好比:最後觸摸屏幕)的手指。這也就是爲何 changedTouches 裏只有一個 touch 對象的緣由,由於某一時刻下老是隻有一個手指在變化。
可是也不能以偏蓋全,由於 touchmove 事件中的 changedTouches 裏就不僅一個,而是跟 touches 和 targetTouches 一樣有多個 touch 對象。
正如前面所說的 touchend 只有 changedTouches 列表裏只有一個 touch 對象,這個對象對應着最後一根手指發生的改變(好比:最後離開屏幕)的手指。
經過研究單指操做跟多指操做,就可讓咱們對 touch 事件瞭解得更加立體,到位。
在移動端手指操做時會默認觸發一些行爲,好比:滾動,縮放。上面的例子是沒有阻止觸摸事件的默認行爲的。因此當你測試上面 multi-finger-touchmove.html 這個例子時,你會發現有時候你會感到很無助,頁面很容易發生縮放行爲,甚至影響到測試效果。要想阻止觸摸事件的默認行爲也很是地簡單隻須要添加以下代碼就能夠了:
document.addEventListener("touchstart", function (ev) { ev.preventDefault(); });
添加觸摸事件的阻止默認行爲的好處也不只僅只有這一個。
1.在IOS 10 下設置 meta 禁止用戶縮放是沒有效果的,使用ev.preventDefault(); 就能夠實現禁止用戶縮放頁面。
2.解決 IOS 10 下溢出隱藏(不起做用)的問題。
3.禁止系統默認的滾動條(如:橫向滾動條)、以及橡皮筋效果。
4.禁止長按選中文字、選中圖片、系統默認菜單。
5.解決點透問題。
雖然有那麼多好處,須要注意的是此時也會帶來一些問題,好比:input 不能獲取焦點了。不過你能夠經過單獨的給 input 標籤添加 touchstart 事件,而且阻止其冒泡就可讓 input 標籤重生了。
var inputObj = document.getElementsByTagName("input")[0]; document.addEventListener("touchstart", function (ev) { ev.preventDefault(); }); inputObj.addEventListener("touchstart", function (ev) { ev.stopPropagation(); });
這裏有一個 Demo,經過 touch 的相關事件實現的一個移動端焦點圖切換效果 :http://yunkus.com/demo/mobile-touch-event/。