眾所周知,安卓手機上touch事件一直有各種各樣莫名其妙的問題。
比如,我想要用swipeLeft/swipeRight監聽向左向右滑動事件,如果只是單純為元素增加swipeLeft/swipeRight事件的話在webview下是不生效的。google了下,還是有解決方法的。如果這個頁面不需要上下滑動的話,完全可以用
$('body').bind("touchmove", function(e) { e.preventDefault(); });
解決。即取消body的touchmove默認行為即可。(為什么取消body的touchmove默認行為就能讓swipe生效呢?)
但這種做法太絕對太暴力了,如果頁面需要上下滑動的話,那就會出問題。這里有兩種情況,第一種是頁面內的某個元素需要上下滑動,另一種是頁面需要上下滑動。
第一種情況,只是頁面內某個元素需要上下滑動的話,可為這個元素監聽touchmove事件,阻止冒泡。比如:
$(id).bind("touchmove", function(e) { e.stopPropagation() });
這樣這個元素就可以實現上下滑動了。
那如果是整個頁面需要上下滑動呢?這就比較棘手了。
我一開始的方案是這樣的。不監聽swipe事件,全部用touch(touchStart/touchmove/touchEnd)實現,在touchStart事件里獲取點擊時的坐標位置(startX, startY),在touchEnd事件里獲取手指離開時的坐標位置(endX, endY)。得到手指滑動的距離(distanceX , distanceY);
distanceX = startX - endX;
distanceY = startY - endY;
absoluteX = Math.abs(distanceX); //橫向距離絕對值
absoluteY = Math.abs(distanceY); //縱向距離絕對值
比較absoluteX和absoluteY的大小,
absoluteY大則為上下滑動,不采取任何處理,
absoluteX大則為左右滑動,此時再比較startX - endX為正還是為負,正的話則則向左滑動,負的話為向右滑動。大概代碼為這樣
var startX, startY; var endX, endY; var distanceX, distanceY; $('body').bind('touchstart', function(event) { startX = event.targetTouches[0].clientX; startY = event.targetTouches[0].clientY; }).bind('touchend', function(event) { endX = event.changedTouches[0].clientX; endY = event.changedTouches[0].clientY; distanceX = Math.abs(startX - endX); distanceY = Math.abs(startY - endY); if (distanceX > distanceY) { startX - endX > 0 ? swipeLeft() : swipeRight(); } });
本以為這樣應該沒什么問題了,結果···
在安卓webview下我模擬的swipe手勢並不會觸發touchend事件,我想這應該也是zepto自己封裝的swipe事件失效的原因。
比如,我只點擊了一下屏幕,其實就相當於執行了touchStart,緊接着執行了touchEnd。但如果我的手指在頁面上進行了滑動操作,他就相當於執行了touchStart,緊接着執行了touchmove,然而手指離開時並不會執行touchEnd事件。那什么情況下它才會執行touchEnd事件呢?
答案是——把touchmove事件的默認行為取消的時候。(為什么執行了touchmove就不會執行touchend了呢?)
所以,還要對body的touchmove事件進行處理。思路是在用戶剛開始滑動的時候,判斷用戶是想上下滑動還是左右滑動,上下滑動的話不做處理,左右滑動的話,對touchmove事件進行preventDefault()操作。如何判斷用戶剛開始滑動時是想左右還是想上下呢,可通過用戶一開始滑動時X軸和Y軸方向的絕對距離進行判斷。具體代碼如下:
var count = 0; //判斷用戶是否第一次進行touchmove操作 var startX, startY; var endX, endY; var distanceX, distanceY; $('body').bind('touchstart', function(event) { count = 0; //每次開始點擊時清零 startX = event.targetTouches[0].clientX; startY = event.targetTouches[0].clientY; }).bind('touchmove', function(event) { if (count === 0) { //如果是第一次滑動 endX = event.changedTouches[0].clientX; endY = event.changedTouches[0].clientY; distanceX = Math.abs(startX - endX); distanceY = Math.abs(startY - endY); if (distanceX > distanceY) { //如果X絕對距離大於Y絕對距離 event.preventDefault(); } } count++; }).bind('touchend', function(event) { endX = event.changedTouches[0].clientX; endY = event.changedTouches[0].clientY; distanceX = Math.abs(startX - endX); distanceY = Math.abs(startY - endY); if (distanceX > distanceY) { startX - endX > 0 ? swipeLeft() : swipeRight(); } });
問題完美解決。現在頁面既可上下滑動也可左右滑動。
補充:
之后google時無意間發現一篇文章(http://www.cnblogs.com/zldream1106/p/mobile_scroll_end.html)
里面有說到swipe的問題
IOS
當"swipe"時,依次產生如下事件:
touchstart -> touchmove * 多次 -> touchend -> scroll
Android
當"swipe"時,swipe雖然不會觸發touchend事件,但是會在scroll事件之前觸發一次touchcancel事件,即:
touchstart -> touchmove -> touchcancel->scroll*多次
Android端的話親測確實如此,touchmove觸發一次,touchcancel觸發一次。IOS端的目前暫時還沒自測過。
所以,剛那個問題在安卓端的話方法二是可行的,只要把touchend改為touchcancel,但為了兼容起見,還是用方法三比較穩妥。
但我還沒明白為什么在安卓webview下swipe沒有觸發touchend事件,這個有待研究。