最近在項目中遇到一個奇怪的問題,有一個需求是這樣:頁面上有一個按鈕,滾動頁面時讓它消失,停止滾動時讓它顯示。
常規思路:
step1、監聽touchstart事件,記錄Touch對象中pageY初始值startY;
step2、監聽touchmove事件,記錄Touch對象中pageY的變化后的值endY,當大於(endY-startY)的絕對值大於某個閾值時隱藏按鈕;
step3、監聽touchend事件,當觸發touchend時,展現按鈕
代碼如下:
var startY,endY; $("body").on("touchstart", touchStartRecord) .on("touchmove", touchMoveHide) .on("touchend", touchEndShow);
function touchStartRecord(event){ var touch = event.touches[0]; startY = touch.pageY; }; function touchMoveHide(event){ var touch = event.touches[0]; endY = touch.pageY; if (Math.abs(endY - startY) >= 5) { //此處省略隱藏按鈕的操作 } }; function touchEndShow(event){ //此處省略重新展現按鈕的操作 };
我們想得思路很清晰簡潔,並且在iPhone上能順利實現我們要的效果,但是尼瑪到了安卓上,手指離開屏幕后,竟然按鈕沒有展現!!??WTF!
用工具調試,發現在觸發touchend事件的函數里打斷點,竟然進不去!!!
所以產生這一問題的原因找到了:touchend事件未被觸發!
如何解決?
在stackoverflow上已經有相關話題的討論,不少人提到,這個問題由來已久,已經給谷歌提bug了(谷歌傳送門:WebView touchevents are not fired propperly if e.preventDefault() is not used on touchstart and touchmove),但是最新的安卓版本還是沒修復……再次WTF!!!
在討論中有提到如下兩種解決方案:
解決方案1:
在監聽touchstart或者touchmove事件的函數里,阻止事件的默認行為event.preventDefault(),那么到touchend就能正常觸發。
代碼如下:
var startY,endY; $("body").on("touchstart", touchStartRecord) .on("touchmove", touchMoveHide) .on("touchend", touchEndShow); function touchStartRecord(event){ var touch = event.touches[0]; startY = touch.pageY; }; function touchMoveHide(event){ var touch = event.touches[0]; endY = touch.pageY; if (Math.abs(endY - startY) >= 5) { //此處省略隱藏按鈕的操作 event.preventDefault(); } }; function touchEndShow(event){ //此處省略重新展現按鈕的操作 };
尼瑪,滾不動了啊……由於移動端touchmove事件的默認行為就是滾動頁面,我們給阻止掉了,touchend是觸發了,但是不是我們想要的效果。第三次WTF!!!
國外知名插件mobiscroll的博客里有分享關於這個問題的一些處理經驗:(傳送門:Working with touch events)
On Android ICS if no preventDefault
is called on touchstart
or the first touchmove
, furthertouchmove
events and the touchend
will not be fired. As a workaround we need to decide in the first touchmove
if this is a scroll (so we don’t call preventDefault
) and then manually trigger touchend。
大意是:在安卓4.0系統(即Android ICS系統),如果在touchstart和第一個touchmove觸發時,沒有調用preventDefault,那么后面touchmove(連續觸發)以及最后的touchend都不會被觸發。所以我們需要決定第一個touchmove是否是一個滾動事件(如果是,則不能preventDefault阻止默認行為)然后手動觸發touchend。
解決方案2:
同時綁定touchcancel和touchend事件,這樣在安卓上就能通過觸發touchcancel來重新展示我們的按鈕。
在touchcancel卻能正常觸發,而在我們的這個需求里,touchcancel的情況下,我們也是希望按鈕重新展現的,那不正好就是我們想要的效果嗎?
代碼如下:
var startY,endY; $("body").on("touchstart", touchStartRecord) .on("touchmove", touchMoveHide) .on("touchcancel touchend", touchEndShow); function touchStartRecord(event){ var touch = event.touches[0]; startY = touch.pageY; }; function touchMoveHide(event){ var touch = event.touches[0]; endY = touch.pageY; if (Math.abs(endY - startY) >= 5) { //此處省略隱藏按鈕的操作 } }; function touchEndShow(event){ //此處省略重新展現按鈕的操作 };
好了,現在能夠解決我們的需求了,但其實還不是最優解,因為我們如果還想給touchcancel單獨增加一個操作,就不能夠了。所以最根本的還是寄希望於谷歌盡早解決這個歷史遺留bug。