移动端 Touch 事件介绍
本文主要介紹 TouchEvent 相關(guān)的一些對象與屬性如 Touch, TouchList, touhces, targetTouches 等,以及使用的注意點和誤區(qū)。
觸摸事件有以下幾種類型:touchstart,touchmove,touchend這三種用的比較多,還有不常用的touchcancel事件。當(dāng)然 MDN上還介紹了touchenter,touchleave事件,具體適用的場景及兼容性如何還未做測試,感興趣的可自行研究。
js中不同的事件類型,event對象包含的屬性也有所差異。我們先了解幾個TouchEvent涉及的對象。
提示:文中的demo都是在 chrome 模擬器,iPhone6s(iOS9.3.2) safari,iOS微信上運行,安卓的兼容性未做測試
Touch
Touch對象代表一個觸點,可以通過event.touches[0]獲取,每個觸點包含位置,大小,形狀,壓力大小,和目標(biāo) element屬性。
{
screenX: 511,
screenY: 400,//觸點相對于屏幕左邊沿的Y坐標(biāo)
clientX: 244.37899780273438,
clientY: 189.3820037841797,//相對于可視區(qū)域
pageX: 244.37,
pageY: 189.37,//相對于HTML文檔頂部,當(dāng)頁面有滾動的時候與clientX=Y 不等
force: 1,//壓力大小,是從0.0(沒有壓力)到1.0(最大壓力)的浮點數(shù)
identifier: 1036403715,//一次觸摸動作的唯一標(biāo)識符
radiusX: 37.565673828125, //能夠包圍用戶和觸摸平面的接觸面的最小橢圓的水平軸(X軸)半徑
radiusY: 37.565673828125,
rotationAngle: 0,//它是這樣一個角度值:由radiusX 和 radiusY 描述的正方向的橢圓,需要通過順時針旋轉(zhuǎn)這個角度值,才能最精確地覆蓋住用戶和觸摸平面的接觸面
target: {} // 此次觸摸事件的目標(biāo)element
}
identifier
這個屬性大家可能有疑惑,使用 Chrome 的模擬器發(fā)現(xiàn)多次觸摸動作,值始終不變。用真機測試則不會有問題(我這里用的safari連接mac調(diào)試)。每次觸摸包括start,move,end這整個過程,標(biāo)志符都不變。下一次觸摸動作開始,標(biāo)志符就會變化。
screenY clientY
在 safari 中 screenY與clientY值是相等的,在iOS微信中兩個數(shù)值不等,但單位應(yīng)該也不一樣。
radiusX radiusY rotationAngle
測試過程中safari及微信內(nèi)置瀏覽器都不支持這些屬性,chrome模擬器可以。
TouchList
由Touch對象構(gòu)成的數(shù)組,通過event.touches取到。一個Touch對象代表一個觸點,當(dāng)有多個手指觸摸屏幕時,TouchList就會存儲多個Touch對象,前面說到的identifier就用來區(qū)分每個手指對應(yīng)的Touch對象。
TouchEvent
TouchEvent就是用來描述手指觸摸屏幕的狀態(tài)變化事件,除了一般DOM事件中event對像具備的屬性,還有一些特有的屬性。
touches
一個TouchList對象,包含當(dāng)前所有接觸屏幕的觸點的Touch對象,不論 touchstart 事件從哪個elment上觸發(fā)。
targetTouches
也是一個TouchList對象,包含了如下觸點的 Touch 對象:touchstart從當(dāng)前事件的目標(biāo)element上觸發(fā)
這里大家可能產(chǎn)生了疑惑,這兩個對象到底有什么區(qū)別?尤其是我們使用chrome模擬器中運行 demo,打印兩個對象發(fā)現(xiàn)他們其實是一樣的。
這兩個對象的區(qū)別可以類比event.target與event.currentTarget 的區(qū)別,如果以前沒留意,自行 js 高級程序設(shè)計。
我們先看一個 demo2,來了解 touch 事件的特性。
在線編輯: http://jsrun.net/3XKKp
預(yù)覽地址: http://jsrun.net/rtd/3XKKp
大家進行以下兩個操作,觀察控制臺發(fā)現(xiàn)了什么?
操作一:一根手指觸摸藍色box,并滑動,繼續(xù)滑動出藍色box
操作二:一根手指觸摸非藍色box區(qū)域,然后慢慢滑動到藍色box
大家會發(fā)現(xiàn):操作一中即使滑出藍色
box,而touchmove,touchend事件會繼續(xù)觸發(fā),touches,targetTouches存儲著相同的 Touch 對象,touchmove事件的目標(biāo)元素仍然是box。
操作二中相關(guān)的 touch 事件都不會觸發(fā)。很神奇的是 touchmove 事件,明明在 box 上滑動,卻不會觸發(fā) touchmove 事件。
我們可以猜測,touch相關(guān)的事件是一個整體,一開始touchstart不可能被觸發(fā),則后續(xù)touch事件也不會被觸發(fā)。當(dāng)然你可以不監(jiān)聽 touchstart 事件,按照操作一 touchmove,touchend 還是可以觸發(fā)的。
再看下面這個demo2
在線編輯:http://jsrun.net/XXKKp
訪問地址:http://jsrun.net/rtd/XXKKp
這里我們對白色區(qū)域body也添加了 touch 事件的監(jiān)聽,繼續(xù)上述 demo1中的兩個操作。
我們可以發(fā)現(xiàn):
操作一可以發(fā)現(xiàn):touch 相關(guān)的事件可以冒泡,觸發(fā)了
box,body的touch事件。操作二只能觸發(fā) body 的touch 事件,和demo1同理。
我們可以觀察下操作一的兩個對象TouchEvent.targetTouches,TouchEvent.touches,無論是box還是body觸發(fā)的 touch 事件,他們的存儲的 Touch對象都是相同的,而且 target 都是 box。
接下來進行操作三:
用兩根手指,一根手指觸摸藍色
box,另一根觸摸白色區(qū)域,然后滑動。
然后再次比較下targetTouches和touches,就可以發(fā)現(xiàn)他們的不同。
changedTouches
也是一個 TouchList 對象,對于 touchstart 事件, 這個 TouchList 對象列出在此次事件中新增加的觸點。對于 touchmove 事件,列出和上一次事件相比較,發(fā)生了變化的觸點。對于 touchend ,列出離開觸摸平面的觸點(這些觸點對應(yīng)已經(jīng)不接觸觸摸平面的手指)。
touchend這里要特別注意,touches和targetTouches只存儲接觸屏幕的觸點,要獲取觸點最后離開的狀態(tài)要使用changedTouches。
之前也經(jīng)常用touches[0]來獲取Touch 對象,如果知道了 touches,targetTouches,changedTouches 的不同之處。在編寫代碼時可以更好的選擇使用,程序也可以更嚴(yán)謹(jǐn)。
touch事件封裝
(function () {
var coord={},
start={},
el;
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove',touchMove);
document.addEventListener('touchend',touchEnd);
document.addEventListener('touchcanel',touchCancel);
function newEvent(type){
return new Event(type,{ bubbles: true,cancelable: true});
}
function touchCancel () {
coord = {}
}
function touchStart(e){
var c = e.touches[0];
start = {
x: c.clientX,
y: c.clientY,
time: Date.now()
};
el= e.target;
el='tagName' in el ? el : el.parentNode;
}
function touchMove(e){
var t = e.touches[0];
coord = {
x: t.clientX - start.x,
y: t.clientY - start.y
}
}
function touchEnd(){
var touchTimes = Date.now() - start.time,
c = 250 > touchTimes && Math.abs(coord.x) > 20 || Math.abs(coord.x) > 80,
s = 250 > touchTimes && Math.abs(coord.y) > 20 || Math.abs(coord.y) > 80,
left = coord.x < 0,
top = coord.y < 0;
if (250 > touchTimes && (isNaN(coord.y) || Math.abs(coord.y)) < 12 && (isNaN(coord.x) || Math.abs(coord.x) < 12)) {
el.dispatchEvent(newEvent('tap'));
}else if(750<touchTimes && (isNaN(coord.y) || Math.abs(coord.y)) < 12 && (isNaN(coord.x) || Math.abs(coord.x) < 12)){
el.dispatchEvent(newEvent('longTap'));
}
c ? el.dispatchEvent(left ? newEvent('swipeLeft') : newEvent('swipeRight')) : s && el.dispatchEvent(top ? newEvent('swipeUp') : newEvent('swipeDown'));
coord={};
}
}());
移動端 Touch 事件的使用與思考(1)
touch.js()原生JS,支持tap,longTap,swipeLeft,swipeRight,SwipeTop,swipeDown事件。
移動項目開發(fā)過程中,經(jīng)常需要用到滑動的事件來處理一些效果。通常情況下,我們會通過 ?touchstart->touchmove->touchend ?的過程來定義這個事件。這些事件的觸發(fā)順序是 ?touchstart, touchmove, touchmove ….. touchend ?。絕大部分平板或手機也正如我們想象的那樣有序執(zhí)行著。但是以Android 4.0.4為首的一些可惡分子卻有些不聽話:他們的touchend事件沒有如預(yù)期的那樣觸發(fā)。
監(jiān)聽這些事件我們會發(fā)現(xiàn),當(dāng)只是輕點一下屏幕時,touchend可以正常觸發(fā)。但是只要當(dāng) touchmove 被觸發(fā)之后,touchend 就不會再被觸發(fā)了,而且 touchmove 也沒有持續(xù)觸發(fā)。
在網(wǎng)上搜集了一些資料顯示,這是 Android 上瀏覽器的bug
> On Android ICS if no preventDefault is called on touchstart or the firsttouchmove,
> further touchmove events and the touchend will not be fired.
正如提到的我們只需要在 touchstart 或者 touchmove 里執(zhí)行一下 e.preventDefault(); 就可以避免這個bug。但是,問題來了:添加了 preventDefault 之后,正常的scroll事件也被屏蔽了!我們意外的發(fā)現(xiàn)滾動條也不能用了!
于是,我們開始嘗試各種添加preventDefault事件的時機:閉包,延遲,判斷等一一用上。最終焦點落在了firsttouchmove上,于是有了以下代碼。
var touchY = 0;
$(this).on('touchstart', function(e){
var touch = e.touches[0];
touchY = touch.clientY;
}).on('touchmove', function(e){
var touch = e.touches[0]
if(Math.abs(touch.clientY - touchY) < 10){
e.preventDefault();
}
}).on('touchend', function(){
// 你的滑動事件
});
基本上主要的思想就是在 touchmove 的時候判斷出縱軸的位移量,當(dāng)小于某個值的時候就認(rèn)為是在執(zhí)行左右滑動,且需要執(zhí)行 preventDefault 來確保 touchend 可以正常觸發(fā)。當(dāng)然,如果你有橫向滾動條且想綁定上下滑動事件的話就需要自己修改一下代碼。
touchEnd事件在Android的某些機子上兼容性不是很好,有些無法觸發(fā)的bug,看一下這篇文章是怎么解決:徹底解決低端安卓手機touchend事件不觸發(fā)(考慮scroll)
本次移動端開發(fā)時遇見了安卓4.2系統(tǒng)不能觸發(fā)touchend的問題,有以下需求。
1. 橫滑輪播圖
2.下拉刷新頁面內(nèi)容
3.body滾動條不能失效
開始在輪播圖touchmove事件中阻止了瀏覽器默認(rèn)行為,此時touchend事件可以觸發(fā)。
//拖拽輪播圖
parentNode.addEventListener('touchmove',function(e) {
e.preventDefault();
})
然后復(fù)制了一份在下拉刷新事件中(此時下拉刷新也OK了)
//下拉刷新代碼
document.addEventListener('touchmove', function(e) {
if (getTopDistance() <= 10) {
e.preventDefault();
}
});
不過此時新的問題又出來了,頁面竟然不能上下滾動了,經(jīng)過分析得出結(jié)論在document的touchmove事件中阻止了瀏覽器默認(rèn)行為導(dǎo)致頁面不能上下滑動。
最終參考了老外的一篇文章解決此問題。(橫滑炒過7認(rèn)為是拖拽錄播圖)
parentNode.addEventListener('touchmove',function(e) {
var _x = e.touches[0].pageX;
if((Math.abs(_x-parentNode.startX)>7)){
e.preventDefault();
}
e.stopPropagation();
})
下拉刷新時也加上判斷條件決定是否阻止瀏覽器默認(rèn)行為(豎直滾動超過10阻止瀏覽器默認(rèn)行為)
document.addEventListener('touchmove', function(e) {
if (getTopDistance() <= 10) {//當(dāng)滾動條位置小于10
// alert('<');
var _x = e.touches[0].pageX;
var _y = e.touches[0].pageY;
if (_y - obj.y > 10) {//滾動距離大于10
e.preventDefault();
}
}
});
/*獲得滾動條位置
*/
function getTopDistance() {
return document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
}
總結(jié)
以上是生活随笔為你收集整理的移动端 Touch 事件介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于苹果二手手机你真的了解吗?
- 下一篇: 华为手机如何查询激活时间