1. 程式人生 > >手機web開發,click,touch,tap事件淺析

手機web開發,click,touch,tap事件淺析

三、touch事件touch是針對觸屏手機上的觸控事件。現今大多數觸屏手機webkit核心提供了touch事件的監聽,讓開發者可以獲取使用者觸控式螢幕幕時的一些資訊。

其中包括:touchstart,touchmove,touchend,touchcancel 這四個事件

touchstart,touchmove,touchend事件可以類比於mousedown,mouseover ,mouseup的觸發。

touchstart : 當手指觸控到螢幕會觸發;

touchmove : 當手指在螢幕上移動時,會觸發;

touchend : 當手指離開螢幕時,會觸發;

touchcancel許多人不知道它在什麼時候會被觸發而忽略它,其實當你的手指還沒有離開螢幕時,有系統級的操作發生時就會觸發touchcancel,例如alert和confirm彈框,又或者是

Android系統的功能彈窗。

這4個事件的觸發順序為:

touchstart -> touchmove -> …… -> touchmove ->touchend

但是單憑監聽上面的單個事件,不足以滿足我們去完成監聽在觸屏手機常見的一些手勢操作,如雙擊、長按、左右滑動、縮放等手勢操作。需要組合監聽這些事件去封裝對這類手勢動作。

其實市面上很多框架都針對手機瀏覽器封裝了這些手勢,例如jqmobile、zepto、jqtouch,不過悲劇發生了,對於某些android系統(我自己測試到的在android 4.0.x),touchmove和touchend事件不能被很好的觸發,舉例子說明下:

比如手指在螢幕由上向下拖動頁面時,理論上是會觸發 一個 touchstart ,很多次 touchmove ,和最終的 touchend ,可是在android 4.0上,touchmove只被觸發一次,觸發時間和

暫時我只發現在android 4.0會有這個bug,據說 ios 3.x的版本也會有。

而顯然jqmobile、zepto等都沒有意識到這個bug對監聽實現帶來的嚴重影響,所以在直接使用這些框架的event時,或多或少會出現相容性問題!(個人親身慘痛經歷)

所以我修改了一下zepto的event模組,並且添加了一些事件觸發引數,加強了一下可用性。

(function($){

$.fn.touchEventBind = function(touch_options)

{

var touchSettings = $.extend({

tapDurationThreshold : 250,//觸屏大於這個時間不當作tap

scrollSupressionThreshold : 10,//觸發touchmove的敏感度

swipeDurationThreshold : 750,//大於這個時間不當作swipe

horizontalDistanceThreshold: 30,//swipe的觸發垂直方向move必須小於這個距離

verticalDistanceThreshold: 75,//swipe的觸發水平方向move必須大於這個距離

tapHoldDurationThreshold: 750,//swipe的觸發水平方向move必須大於這個距離

doubleTapInterval: 250//swipe的觸發水平方向move必須大於這個距離

}, touch_options || {})

var touch = {}, touchTimeout ,delta ,longTapTimeout;

function parentIfText(node){

return 'tagName' in node ? node : node.parentNode

}

function swipeDirection(x1, x2, y1, y2){

var xDelta = Math.abs(x1 - x2), yDelta = Math.abs(y1 - y2)

return xDelta >= yDelta ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')

}

function longTap()

{

longTapTimeout = null

touch.el.trigger('longTap');

touch.longTap = true;

touch = {};

}

function cancelLongTap()

{

if (longTapTimeout) clearTimeout(longTapTimeout)

longTapTimeout = null

}

this.data('touch_event_bind',"true");

this.bind('touchstart', function(e)

{

touchTimeout && clearTimeout(touchTimeout);

touch.el = $(parentIfText(e.touches[0].target));

now = Date.now();

delta = now - (touch.last_touch_time || now);

touch.x1 = e.touches[0].pageX;

touch.y1 = e.touches[0].pageY;

touch.touch_start_time = now;

touch.last_touch_time = now;

if (delta > 0 && delta <= touchSettings.doubleTapInterval) touch.isDoubleTap = true;

longTapTimeout = setTimeout(function(){

longTap();

},touchSettings.tapHoldDurationThreshold);

}).bind('touchmove',function(e)

{

cancelLongTap();

touch.x2 = e.touches[0].pageX;

touch.y2 = e.touches[0].pageY;

// prevent scrolling

if ( Math.abs( touch.x1 - touch.x2 ) > touchSettings.scrollSupressionThreshold )

{

e.preventDefault();

}

touch.touch_have_moved = true;

}).bind('touchend',function(e)

{

cancelLongTap();

now = Date.now();

touch_duration = now - (touch.touch_start_time || now);

if(touch.isDoubleTap)

{

touch.el.trigger('doubleTap');

touch = {};

}

else if(!touch.touch_have_moved && touch_duration < touchSettings.tapDurationThreshold)

{

touch.el.trigger('tap');

touchTimeout = setTimeout(function(){

touchTimeout = null;

touch.el.trigger('singleTap');

touch = {};

}, touchSettings.doubleTapInterval);

}

else if ( touch.x1 && touch.x2 )

{

if ( touch_duration < touchSettings.swipeDurationThreshold && Math.abs( touch.x1 - touch.x2 ) > touchSettings.verticalDistanceThreshold && Math.abs( touch.y1 - touch.y2 ) < touchSettings.horizontalDistanceThreshold )

{

touch.el.trigger('swipe').trigger( touch.x1 > touch.x2 ? "swipeLeft" : "swipeRight" );

touch = {};

}

}

}).bind('touchcancel',function(e){

touchTimeout && clearTimeout(touchTimeout);

cancelLongTap();

touch = {};

})

}

$.fn.touchbind = function(m,callback,touch_options)

{

if(this.data('touch_event_bind')!="true")

{

this.touchEventBind(touch_options);

}

this.bind(m,callback);

}

 ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(m)

 {

 $.fn[m] = function(touch_options,callback)

 {

 if(typeof(touch_options)=="object" && typeof(callback)=="function")

 {

 return this.touchbind(m, callback , touch_options)

 }

 else if(typeof(touch_options)=="function")

 {

 var callback = touch_options;

 return this.touchbind(m, callback)

 }

 }

 })

})(Zepto)

上面的程式碼基於zepto,替換掉原先zepto的這塊就OK了,不過獨立寫開來也是可以的,我只是用到了zepto的 bind函式來做事件監聽而已,實現的思路其實也很清晰。

相容的解決辦法是在 touchmove 時判斷手勢趨勢大於預設值時(大於預設值證明有 move的動作趨勢),停止預設的操作e.preventDefault(),這樣touchedn就可以被正常觸發了。真心認為google的這個bug是一個極其影響手機web互動的bug!