web开发,click,touch,tap事件浅析
一、click 和 tap 比较
两者都会在点击时触发,但是在手机WEB端,click会有 200~300 ms,所以请用tap代替click作为点击事件。
singleTap和doubleTap 分别代表单次点击和双次点击。
二、关于tap的点透处理
在使用zepto框架的tap来移动设备浏览器内的点击事件,来规避click事件的延迟响应时,有可能出现点透的情况,即点击会触发非当前层的点击事件。
处理方式:
(1)、
github上有一个叫做fastclick的库,它也能规避移动设备上click事件的延迟响应,https://github.com/ftlabs/fastclick
将它用script标签引入页面(该库支持AMD,于是你也可以按照AMD规范,用诸如require.js的模块加载器引入),并且在dom ready时初始化在body上,如:
1
2
3
|
$( function (){ new
FastClick(document.body); }) |
然后给需要“无延迟点击”的元素绑定click事件(注意不再是绑定zepto的tap事件)即可。
当然,你也可以不在body上初始化它,而在某个dom上初始化,这样,只有这个dom和它的子元素才能享受“无延迟”的点击
实践开发中发现,当元素绑定fastclick后,click响应速度比tap还要快一点点。哈哈
(2)、为元素绑定touchend事件,并在内部加上e.preventDefault();
$demo.on(
'touchend'
,
function
(e){
//
改变了事件名称,tap是在body上才被触发,而touchend是原生的事件,在dom本身上就会被捕获触发
$demo.hide()
e.preventDefault();
//
阻止“默认行为”
})
其中包括:touchstart,touchmove,touchend,touchcancel 这四个事件
touchstart,touchmove,touchend事件可以类比于mousedown,mouseover
,mouseup的触发。
touchstart
: 当手指触摸到屏幕会触发;
touchmove : 当手指在屏幕上移动时,会触发;
touchend : 当手指离开屏幕时,会触发;
而touchcancel许多人不知道它在什么时候会被触发而忽略它,其实当你的手指还没有离开屏幕时,有系统级的操作发生时就会触发touchcancel,例如alert和confirm弹框,又或者是android系统的功能弹窗。
例如:
这4个事件的触发顺序为:
touchmove -> …… -> touchmove ->touchend
但是单凭监听上面的单个事件,不足以满足我们去完成监听在触屏手机常见的一些手势操作,如双击、长按、左右滑动、缩放等手势操作。需要组合监听这些事件去封装对这类手势动作。
其实市面上很多框架都针对手机浏览器封装了这些手势,例如jqmobile、zepto、jqtouch,不过悲剧发生了,对于某些android系统(我自己测试到的在android 4.0.x),touchmove和touchend事件不能被很好的触发,举例子说明下:
比如手指在屏幕由上向下拖动页面时,理论上是会触发 一个 touchmove ,和最终的 touchend ,可是在android 4.0上,touchmove只被触发一次,触发时间和touchstart 差不多,而touchend直接没有被触发。这是一个非常严重的bug,在google Issue已有不少人提出 http://code.google.com/p/android/issues/detail?id=19827
暂时我只发现在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!