在前端的移動(dòng)Web開發(fā)中唐断,有一部分事件只在移動(dòng)端產(chǎn)生汁汗,如觸摸相關(guān)的事件。接下來給大家簡(jiǎn)單總結(jié)一下移動(dòng)端的事件栗涂。
1. PC端事件在移動(dòng)端的兼容問題
1.1 click事件的200~300ms延遲問題
由于移動(dòng)端默認(rèn)的布局視口寬度是980像素知牌,所以網(wǎng)頁文字非常小,為了快速讓網(wǎng)頁還原到原來的大小斤程,Safari最新引入了雙擊縮放功能:用戶雙擊手機(jī)頁面的時(shí)候角寸,瀏覽器會(huì)智能的縮放當(dāng)前頁面到原始大小。
?雙擊縮放的原理就是忿墅,當(dāng)用戶click一次之后扁藕,瀏覽器會(huì)經(jīng)過約300ms之后檢測(cè)是否再有一次click,如果有的話疚脐,就會(huì)縮放頁面亿柑。否則的話就是一個(gè)click事件。
由于雙擊縮放功能存在棍弄,click事件觸發(fā)就會(huì)有大約200~300ms的延遲望薄。
1.2 dblclick事件失效
由于雙擊縮放的存在疟游,pc端的dblclick事件也失效了。
2. 移動(dòng)端特有的touch事件
由于移動(dòng)端設(shè)備大都具備觸摸功能痕支,所以移動(dòng)端瀏覽器都引入了觸摸(touch)事件颁虐。
touch相關(guān)的事件跟普通的其他dom事件一樣使用,可以直接用addEventListener來監(jiān)聽和處理卧须。
最基本的touch事件包括4個(gè)事件:
touchstart: 當(dāng)在屏幕上按下手指時(shí)觸發(fā)
touchmove: 當(dāng)在屏幕上移動(dòng)手指時(shí)觸發(fā)
touchend: 當(dāng)在屏幕上抬起手指時(shí)觸發(fā)
touchcancel 當(dāng)一些更高級(jí)別的事件發(fā)生的時(shí)候(如電話接入或者彈出信息)會(huì)取消當(dāng)前的touch操作另绩,即觸發(fā)touchcancel。一般會(huì)在touchcancel時(shí)暫停游戲花嘶、存檔等操作笋籽。
2.1 touch事件與click事件同時(shí)觸發(fā)
在很多情況下,觸摸事件和鼠標(biāo)事件會(huì)同時(shí)被觸發(fā)(目的是讓沒有對(duì)觸摸設(shè)備優(yōu)化的代碼仍然可以在觸摸設(shè)備上正常工作)椭员。
因?yàn)殡p擊縮放檢測(cè)的存在干签,在移動(dòng)設(shè)備屏幕上點(diǎn)擊操作的事件執(zhí)行順序:
touchstart(瞬間觸發(fā)) → touchend → click(200-300ms延遲)
如果你使用了觸摸事件,可以調(diào)用 event.preventDefault()來阻止鼠標(biāo)事件被觸發(fā)拆撼。
2.2 touchstart事件
? 當(dāng)用戶手指觸摸到的觸摸屏的時(shí)候觸發(fā)容劳。事件對(duì)象的 target 就是touch 發(fā)生位置的那個(gè)元素。
<div>
點(diǎn)擊我闸度!
</div>
<script>
var box = document.querySelector("div");
box.addEventListener("touchstart", function (e) {
console.log('touchstart');
});
</script>
2.3 touchmove事件
當(dāng)用戶在觸摸屏上移動(dòng)觸點(diǎn)(手指)的時(shí)候竭贩,觸發(fā)這個(gè)事件。一定是先要觸發(fā)touchstart事件莺禁,再有可能觸發(fā) touchmove 事件留量。
?touchmove 事件的target 與最先觸發(fā)的 touchstart 的 target 保持一致。touchmove事件和鼠標(biāo)的mousemove事件一樣都會(huì)多次重復(fù)調(diào)用哟冬,所以楼熄,事件處理時(shí)不能有太多耗時(shí)操作。不同的設(shè)備浩峡,移動(dòng)同樣的距離 touchmove 事件的觸發(fā)頻率是不同的可岂。
注意:
- 即使手指移出了 原來的target 元素,則 touchmove 仍然會(huì)被一直觸發(fā)翰灾,而且 target 仍然是原來的 target 元素缕粹。
- touchmove事件會(huì)多次重復(fù)觸發(fā),由于移動(dòng)端計(jì)算資源寶貴纸淮,盡量保證事件節(jié)流
<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事件
? 當(dāng)用戶的手指抬起的時(shí)候平斩,會(huì)觸發(fā) touchend 事件。如何用戶的手指從觸屏設(shè)備的邊緣移出了觸屏設(shè)備咽块,也會(huì)觸發(fā) touchend 事件绘面。
touchend 事件的 target 也是與 touchstart 的 target 一致,即使已經(jīng)移出了元素。
2.5 touchcancel事件
? 當(dāng)觸點(diǎn)由于某些原因被中斷時(shí)觸發(fā)揭璃。有幾種可能的原因如下(具體的原因根據(jù)不同的設(shè)備和瀏覽器有所不同):
- 由于某個(gè)事件取消了觸摸:例如觸摸過程被一個(gè)模態(tài)的彈出框打斷晚凿。
- 觸點(diǎn)離開了文檔窗口,而進(jìn)入了瀏覽器的界面元素塘辅、插件或者其他外部?jī)?nèi)容區(qū)域。
- 當(dāng)用戶產(chǎn)生的觸點(diǎn)個(gè)數(shù)超過了設(shè)備支持的個(gè)數(shù)皆撩,從而導(dǎo)致 TouchList 中最早的 Touch對(duì)象被取消
touchcancel 事件一般用于保存現(xiàn)場(chǎng)數(shù)據(jù)扣墩。比如:正在玩游戲,如果發(fā)生了 扛吞。touchcancel 事件呻惕,則應(yīng)該把游戲當(dāng)前狀態(tài)相關(guān)的一些數(shù)據(jù)保存起來。
3. 觸摸事件對(duì)象
TouchEvent
是一類描述手指在觸摸平面(觸摸屏滥比、觸摸板等)的狀態(tài)變化的事件亚脆。這類事件用于描述一個(gè)或多個(gè)觸點(diǎn),使開發(fā)者可以檢測(cè)觸點(diǎn)的移動(dòng)盲泛,觸點(diǎn)的增加和減少濒持,等等。
每 個(gè) Touch
對(duì)象代表一個(gè)觸點(diǎn); 每個(gè)觸點(diǎn)都由其位置寺滚,大小柑营,形狀,壓力大小村视,和目標(biāo) element
描述官套。 TouchList
對(duì)象代表多個(gè)觸點(diǎn)的一個(gè)列表.
3.1 TouchEvent
TouchEvent
的屬性繼承了 UIEvent
和 Event
。
屬性列表:
TouchEvent.changedTouches
: 一個(gè)TouchList
對(duì)象蚁孔,包含了代表所有從上一次觸摸事件到此次事件過程中奶赔,狀態(tài)發(fā)生了改變的觸點(diǎn)的Touch
對(duì)象。TouchEvent.targetTouches
: 一個(gè)TouchList
對(duì)象杠氢,是包含了如下觸點(diǎn)的Touch
對(duì)象:觸摸起始于當(dāng)前事件的目標(biāo)element
上站刑,并且仍然沒有離開觸摸平面的觸點(diǎn)。TouchEvent.touches
: 一 個(gè)TouchList
對(duì)象鼻百,包含了所有當(dāng)前接觸觸摸平面的觸點(diǎn)的Touch
對(duì)象笛钝,無論它們的起始于哪個(gè)element
上,也無論它們狀態(tài)是否發(fā)生了變化愕宋。
<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對(duì)象的屬性和方法
});
}
</script>
3.2 TouchList詳解
? 一個(gè)TouchList
代表一個(gè)觸摸屏幕上所有觸點(diǎn)的列表玻靡。
? 舉例來講, 如果一個(gè)用戶用三根手指接觸屏幕(或者觸控板), 與之相關(guān)的TouchList
對(duì)于每根手指都會(huì)生成一個(gè) Touch
對(duì)象, 共計(jì) 3 個(gè).
-
只讀屬性:
length
返回這個(gè)
TouchList
中Touch
對(duì)的個(gè)數(shù)。(就是有幾個(gè)手指接觸到了屏幕) -
方法:
item(index)
返回
TouchList
中指定索引的Touch
對(duì)象中贝。
<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對(duì)象的個(gè)數(shù)
for(var i = 0; i < e.changedTouches.length; i++){
//遍歷出來每個(gè)Touch對(duì)象
console.log(e.changedTouches.item(i));
}
})
</script>
測(cè)試多個(gè)手機(jī)觸摸屏幕:
<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個(gè)手指在div上
- 先放1個(gè)手指在其他地方囤捻,然后再放1個(gè)手指在
div
上
- 先放1個(gè)手指在其他地方,然后再逐漸放2個(gè)手指在
div
上
3.3 Touch詳解
? Touch
表示用戶和觸摸設(shè)備之間接觸時(shí)單獨(dú)的交互點(diǎn)(a single point of contact
)邻寿。? 這個(gè)交互點(diǎn)通常是一個(gè)手指或者觸摸筆蝎土,? 觸摸設(shè)備通常是觸摸屏或者觸摸板视哑。
基本屬性列表(都是只讀):
編號(hào) | 屬性名 | 屬性說明 |
---|---|---|
1. | identifier |
表示每 1 個(gè) Touch 對(duì)象 的獨(dú)一無二的 identifier 。有了這個(gè) identifier 可以確保你總能追蹤到這個(gè) Touch 對(duì)象誊涯。 |
2. | screenX |
觸摸點(diǎn)相對(duì)于屏幕左邊緣的 x 坐標(biāo)挡毅。 |
3. |
scre enY |
觸摸點(diǎn)相對(duì)于屏幕上邊緣的 y 坐標(biāo)。 |
4. | clientX |
觸摸點(diǎn)相對(duì)于瀏覽器的 viewport 左邊緣的 x 坐標(biāo)暴构。不會(huì)包括左邊的滾動(dòng)距離跪呈。 |
5. | clientY |
觸摸點(diǎn)相對(duì)于瀏覽器的 viewport 上邊緣的 y 坐標(biāo)。不會(huì)包括上邊的滾動(dòng)距離取逾。 |
6. | pageX |
觸摸點(diǎn)相對(duì)于 document 的左邊緣的 x 坐標(biāo)耗绿。 與 clientX 不同的是,他包括左邊滾動(dòng)的距離砾隅,如果有的話误阻。 |
7. | pageY |
觸摸點(diǎn)相對(duì)于 document 的左邊緣的 y 坐標(biāo)。 與 clientY 不同的是晴埂,他包括上邊滾動(dòng)的距離究反,如果有的話。 |
8. | target |
總是表示 手指最開始放在觸摸設(shè)備上的觸發(fā)點(diǎn)所在位置的 element 儒洛。 即使已經(jīng)移出了元素甚至移出了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;
}
}
沒有左右滾動(dòng):
左右滾動(dòng):pageX
明顯大于 clientX
4. 封裝移動(dòng)端tap事件
由于點(diǎn)擊事件經(jīng)常使用奴紧,如果用click會(huì)有延遲問題,一般我們會(huì)用touch事件模擬移動(dòng)端的點(diǎn)擊事件, 以下是封裝的幾個(gè)事件晶丘,僅供參考黍氮。
(function (window){ //傳入window,提高變量的查找效率
function myQuery(selector){ //這個(gè)函數(shù)就是對(duì)外提供的接口浅浮。
//調(diào)用這個(gè)函數(shù)的原型對(duì)象上的_init方法沫浆,并返回
return myQuery.prototype._init(selector);
}
myQuery.prototype = {
/*初始化方法,獲取當(dāng)前query對(duì)象的方法*/
_init: function (selector){
if (typeof selector == "string"){
//把查找到的元素存入到這個(gè)原型對(duì)象上滚秩。
this.ele = window.document.querySelector(selector);
//返回值其實(shí)就是原型對(duì)象专执。
return this;
}
},
/*單擊事件:
* 為了規(guī)避click的300ms的延遲,自定義一個(gè)單擊事件
* 觸發(fā)時(shí)間:
* 當(dāng)抬起手指的時(shí)候觸發(fā)
* 需要判斷手指落下和手指抬起的事件間隔郁油,如果小于500ms表示單擊時(shí)間本股。
* 如果是大于等于500ms,算是長(zhǎng)按時(shí)間
* */
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;
}
}
},
/**
* 長(zhǎng)按
* @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之后執(zhí)行
timerId = setTimeout(function (){
handler.call(this, e);
}, 500)
break;
case "touchmove" :
//如果中間有移動(dòng)也清除定時(shí)器
clearTimeout(timerId)
break;
case "touchend" :
//如果在500ms之內(nèi)抬起了手指桐腌,則需要定時(shí)器
clearTimeout(timerId);
break;
}
}
},
/**
* 左側(cè)滑動(dòng)拄显。
* 記錄手指按下的左邊,在離開的時(shí)候計(jì)算 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方向移動(dòng)大于y方向的移動(dòng)案站,并且x方向的移動(dòng)大于25個(gè)像素躬审,表示在向左側(cè)滑動(dòng)
if (Math.abs(endX - startX) >= Math.abs(endY - startY) && startX - endX >= 25){
handler.call(this, e);
}
break;
}
}
},
/* 右側(cè)滑動(dòng) */
rightLeft: function (e){
//TODO:
}
}
window.$ = window.myQuery = myQuery;
})(window);
// ========================
// 使用:
$("div").tap(function (e){
console.log("單擊事件")
})
$("div").longTag(function (){
console.log("長(zhǎng)按事件");
})
$("div").slideLeft(function (e){
console.log(this);
this.innerHTML = "左側(cè)滑動(dòng)了....."
})
5. 觸摸手勢(shì)封裝相關(guān)的框架及事件
手勢(shì)相關(guān)的事件一般就是tap類(觸屏)和滑動(dòng)(swipe)事件兩類。都是基于原生的touchstart、touchmove承边、touchend事件遭殉,封裝成不同的手勢(shì)類型自定義事件。
5.1 tap類事件
觸碰事件博助,我目前還不知道它和touch的區(qū)別险污,一般用于代替click事件,有tap longTap singleTap doubleTap四種之分富岳。
- tap: 手指碰一下屏幕會(huì)觸發(fā)
- longTap: 手指長(zhǎng)按屏幕會(huì)觸發(fā)
- singleTap: 手指碰一下屏幕會(huì)觸發(fā)
- doubleTap: 手指雙擊屏幕會(huì)觸發(fā)
5.2 swipe類事件
滑動(dòng)事件蛔糯,有swipe swipeLeft swipeRight swipeUp swipeDown 五種之分。
- swipe:手指在屏幕上滑動(dòng)時(shí)會(huì)觸發(fā)
- swipeLeft:手指在屏幕上向左滑動(dòng)時(shí)會(huì)觸發(fā)
- swipeRight:手指在屏幕上向右滑動(dòng)時(shí)會(huì)觸發(fā)
- swipeUp:手指在屏幕上向上滑動(dòng)時(shí)會(huì)觸發(fā)
- swipeDown:手指在屏幕上向下滑動(dòng)時(shí)會(huì)觸發(fā)
5.3 zepto的手勢(shì)相關(guān)事件
Zepto.js 是一個(gè)輕量級(jí)的針對(duì)現(xiàn)代高級(jí)瀏覽器的JavaScript庫城瞎, 它適配了jQuery的大部分api渤闷,也就是jQuery怎么用疾瓮,Zepto.js就怎么用脖镀。它非常小,非常適合移動(dòng)端狼电。
Zepto.js的touch模塊中封裝了手勢(shì)相關(guān)的代碼蜒灰。封裝了再觸摸設(shè)備上觸發(fā)tap– 和 swipe– 相關(guān)事件,也適用于所有的touch
(iOS, Android)和pointer
事件(Windows Phone)肩碟。
- 觸屏事件:tap强窖、singleTap、doubleTap削祈、longTap(>750ms)
- 滑動(dòng)事件: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 其他移動(dòng)端手勢(shì)相關(guān)庫
hammer.js
hammer提供了不僅僅tap、swipe等事件吨拍,還提供了:pan(平移)褪猛、pinch類(捏拿縮放)、 press類(按赘巍)伊滋、 rotate類(旋轉(zhuǎn))類手勢(shì)支持, hammer.js詳解教程
6. 移動(dòng)端點(diǎn)擊穿透問題
如果某個(gè)返回按鈕的位置队秩,恰好在要返回的這個(gè)頁面的帶有href屬性的a標(biāo)簽的范圍內(nèi)笑旺,在點(diǎn)擊返回按鈕后,頁面快速切換到有a標(biāo)簽的頁面馍资,300ms后觸發(fā)了click事件燥撞,從而觸發(fā)了a標(biāo)簽的意外跳轉(zhuǎn),這個(gè)就是典型的點(diǎn)擊穿透問題。罪魁禍?zhǔn)灼鋵?shí)就是a標(biāo)簽跳轉(zhuǎn)默認(rèn)是click事件觸發(fā)物舒,而移動(dòng)端的touch事件觸發(fā)之后色洞,依然會(huì)在300ms后觸發(fā)click事件。
解決辦法:
1.就是阻止觸發(fā)touch事件完成后的click事件冠胯。
2.不要混用touch和click事件火诸。顯然不可能都綁定click事件,因?yàn)橐鉀Q300ms延遲問題(除了fastclick)荠察,那么只能都綁定touch事件置蜀,這樣click事件永遠(yuǎn)不會(huì)被觸發(fā)。
注意:zepto并沒有阻止click事件悉盆,所以使用zepto的tap事件依然會(huì)導(dǎo)致點(diǎn)擊穿透問題盯荤,你需要手動(dòng)添加 e.preventDefault() 來阻止click事件。
參考文章: