一、pc端事件回顧
HTML事件、DOM0事件、DOM2事件
事件對象。
如果上述概念不清楚,請先去了解。
二、移動端事件簡介
2.1 pc端事件在移動端的問題
移動設備主要特點是不配備鼠標,鍵盤也只是在需要輸入的地方纔會激活虛擬鍵盤。所以以前的pc端事件在移動端使用起來就沒有以前那麼爽了,雖然部分仍然可以使用。
click事件的300ms延遲問題。
2007年第一代iphone發佈,由於那個年代所有的網頁都是針對大屏的pc端設計的,iphone的Safari瀏覽器爲了讓用戶瀏覽網頁的時候可以瀏覽到整個網頁,把viewport設置爲960px(參考前面的文章),好是好,但是由於縮放了整個頁面,導致內容變得非常小,視力6.0的都不一定看得清楚。
所以Safari瀏覽器自帶了一個當時看起來相當酷的一個功能:雙擊縮放。你雙擊頁面的時候,瀏覽器會智能的縮放當前頁面到原始大小。
雙擊縮放的原理就是,當你click一次之後,會經過300ms之後檢測是否再有一次click,如果有的話,就會縮放頁面。否則的話就是一個click事件。
所以,當你想執行click操作的時候,就感覺到了”卡頓”。如果點擊之後100ms之後沒有反應,基本就有卡頓的感覺。
dblclick事件失效
由於雙擊縮放的存在,pc端的dblclick事件也失效了。
2.2移動端web新增touch事件
隨着觸屏設備的普及,w3c爲移動端web新增了touch事件。
最基本的touch事件包括4個事件:
touchstart
當在屏幕上按下手指時觸發
touchmove
當在屏幕上移動手指時觸發
touchend
當在屏幕上擡起手指時觸發
touchcancel
當一些更高級別的事件發生的時候(如電話接入或者彈出信息)會取消當前的touch操作,即觸發touchcancel。一般會在touchcancel時暫停遊戲、存檔等操作。
三、相關知識詳解
與移動端相關的interface
主要有三個:
TouchEvent
表示觸摸狀態發生改變時觸發的
event
Touch
表示用戶和觸屏設備之間接觸時單獨的交互點(
a single point of contact
)TouchList
表示一組
touches
。當發生多點觸摸的時候才用的到。
3.1、TouchEvent詳解
爲了區別觸摸相關的狀態改變,存在多種類型的觸摸事件。可以通過檢查觸摸事件的 TouchEvent.type
屬性來確定當前事件屬於哪種類型。
注意:在很多情況下,觸摸事件和鼠標事件會同時被觸發(目的是讓沒有對觸摸設備優化的代碼仍然可以在觸摸設備上正常工作)。如果你使用了觸摸事件,可以調用 event.preventDefault()
來阻止鼠標事件被觸發。
var i = 1;
var box = document.querySelector("div");
var ps = document.querySelectorAll("p");
box.addEventListener("touchstart", function (e){
ps[0].innerHTML = e.type + i++;
})
box.addEventListener("touchmove", function (e){
ps[1].innerHTML = e.type + i++;
})
box.addEventListener("touchend", function (e){
ps[2].innerHTML = e.type + i++;
})
3.1.1 touchstart
當用戶手指觸摸到的觸摸屏的時候觸發。事件對象的 target
就是touch
發生位置的那個元素。
<div>
<p></p>
</div>
<script>
var i = 1;
var box = document.querySelector("div");
var p = document.querySelector("p");
box.addEventListener("touchstart", function (e){
p.innerHTML = e.target.tagName + ", " + i++; // P
})
</script>
3.1.2 touchmove
當用戶在觸摸屏上移動觸點(手指)的時候,觸發這個事件。一定是先要觸發touchstart
事件,再有可能觸發 touchmove
事件。
touchmove
事件的target
與最先觸發的 touchstart
的 target
保持一致。touchmove
事件和鼠標的mousemove
事件一樣都會多次重複調用,所以,事件處理時不能有太多耗時操作。不同的設備,移動同樣的距離 touchmove
事件的觸發頻率是不同的。
有一點需要注意:即使手指移出了 原來的target
元素,則 touchmove
仍然會被一直觸發,而且 target
仍然是原來的 target
元素。
<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>
3.1.3 touchend
當用戶的手指擡起的時候,會觸發 touchend
事件。如何用戶的手指從觸屏設備的邊緣移出了觸屏設備,也會觸發 touchend
事件。
touchend
事件的 target
也是與 touchstart
的 target
一致,即使已經移出了元素。
3.1.4 touchcancel
當觸點由於某些原因被中斷時觸發。有幾種可能的原因如下(具體的原因根據不同的設備和瀏覽器有所不同):
- 由於某個事件取消了觸摸:例如觸摸過程被一個模態的彈出框打斷。
- 觸點離開了文檔窗口,而進入了瀏覽器的界面元素、插件或者其他外部內容區域。
- 當用戶產生的觸點個數超過了設備支持的個數,從而導致
TouchList
中最早的Touch
對象被取消
touchcancel
事件一般用於保存現場數據。比如:正在玩遊戲,如果發生了 touchcancel
事件,則應該把遊戲當前狀態相關的一些數據保存起來。
3.2 TouchList詳解
一個TouchList代表一個觸摸屏幕上所有觸點的列表。
舉例來講, 如果一個用戶用三根手指接觸屏幕(或者觸控板), 與之相關的TouchList
對於每根手指都會生成一個 Touch
對象, 共計 3 個.
TouchList有1個屬性,2個方法
只讀屬性:
length
返回這個
TouchList
中Touch
對的個數。(就是有幾個手指接觸到了屏幕)方法:
identifiedTouch()
返回值是在
TouchList
中的第 1 個Touch
對象,已經被標記爲過時,雖然有的瀏覽器還可以使用,但是不建議使用。方法:
item(index)
返回
TouchList
中指定索引的Touch
對象。
想獲取
TouchList
對象,TouchEvent
的三個屬性可以獲取到TouchList
:
changedTouches
:(只讀)這個
TouchList
對象列出了和這個觸摸事件對應的那些發生了變化的Touch
對象。- 對於
touchstart
事件, 這個TouchList
對象列出在此次事件中新增加的觸點。 - 對於
touchmove 事件,列出和上一次事件相比較,發生了變化的觸點。
- 對於 touchend 列出離開觸摸平面的觸點(這些觸點對應已經不接觸觸摸平面的手指)
- 對於
targetTouches
:(只讀)這個
TouchList
列出了那些touchstart
發生在這個元素,並且還沒有離開touch surface
的touch point
(手指)。是touches
的一個嚴格子集。touches`:(只讀)
這個
TouchList
列出了事件觸發時:touch suface
上所有的touch point
。
- 看下面的代碼:
<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詳解
表示用戶和觸摸設備之間接觸時單獨的交互點(a single point of contact
)。
這個交互點通常是一個手指或者觸摸筆。
觸摸設備通常是觸摸屏或者觸摸板。
js對象提供了多個屬性來描述
Touch
對象。這些屬性分成兩大部分:
基本屬性(
Basic properties
)觸摸區域相關屬性 (
Touch area
)
Touch area
目前還在試驗階段。我們先不做過多的討論。基本屬性:
這些屬性均是隻讀屬性
identifier
:表示每 1 個
Touch
對象 的獨一無二的identifier
。有了這個identifier
可以確保你總能追蹤到這個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){ var touchList = e.changedTouches; for (var i = 0; i < touchList.length; i++){ console.log(touchList.item(i).identifier); } }) </script>
screenX
:觸摸點相對於屏幕左邊緣的
x
座標。scre
enY:觸摸點相對於屏幕上邊緣的
y
座標。clientX
:觸摸點相對於瀏覽器的
viewport
左邊緣的x
座標。不會包括左邊的滾動距離。clientY
:觸摸點相對於瀏覽器的
viewport
上邊緣的y
座標。不會包括上邊的滾動距離。pageX
:觸摸點相對於
document
的左邊緣的x
座標。 與clientX
不同的是,他包括左邊滾動的距離,如果有的話。pageY
:觸摸點相對於
document
的左邊緣的y
座標。 與clientY
不同的是,他包括上邊滾動的距離,如果有的話。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
四、封裝移動端事件
前面的是最基本的事件,直接使用相對比較麻煩,所以有必要對一些常用事件進行封裝。
比如:單擊事件、雙擊事件、滑動方向等
(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 = "左側滑動了....."
})
參考資料:mdn相關文檔