点击穿透

  1. What? -> Events!
  2. Why?
  3. How to fix?

说在前面的话:点击穿透这种现象算是移动H5开发比较经典的问题,也常用来做面试题。借此现象谈谈移动事件。

What

现象描述: 页面中存在上下两个层,上层元素具有表单,链接或者绑定相应事件,上层元素点击或触摸,导致上层DOM改变,下层中同样位置的 元素触发点击事件。这种现象就是点击穿透(Ghost Clicks)。其实我觉得用“幽冥点击”称呼它更加带感。

要理解这种现象,首先要了解移动端的相关事件 —— 触摸(Touch)事件,点击(Click)事件。

Touch 事件中,常用的为 touchstart, touchmove, touchend 三种。除此之外还有touchcancel。 注意,原生事件中并没有tap事件。下面会解释tap事件怎么产生的。

事件描述如下:

事件描述触发时机
touchstart开始触摸手指接触屏幕时立即触发
touchmove移动或拖拽取决于系统和浏览器
touchend触摸结束手指离开屏幕时立即出发

而Touch事件的触发一般通过手指,还会存在多点触控,拖拽方向等情况。列出几个重要参数如下:

参数含义
touches屏幕中每根手指信息列表
targetTouches和touches类似,把同一节点的手指信息过滤掉
changedTouches响应当前事件的每根手指的信息列表

代码获取如下:

xxDOM.addEventListener('touchstart', function(e) {
   console.log(e.touches, e.targetTouches, e.changedTouches);
});

手指触发触摸事件的过程如下:

touchstart -> touchmove -> ... touchmove -> touchend

由此,我们可以在 ontouchstart 事件上记录开始触摸开始,ontouchend 记录触摸结束信息。 通过上述这些参数,很容易的去计算幽冥点击的时间,以及点击穿透的相关信息,包括响应的坐标情况。

Why

问题来了,click 事件什么时候触发?

浏览器在 touchend 之后会等待约 300ms ,如果没有 tap 行为,则触发 click 事件。 而浏览器等待约 300ms 的原因是,判断用户是否是双击(double tap)行为,双击过程中就不适合触发 click 事件了。 由此可以看出 click 事件触发代表一轮触摸事件的结束。

上面说到原生事件中并没有 tap 事件,可以参考经典的 zepto.js 对 singleTap 事件的处理。见源码 136-143 行

可以看出,singleTap 事件的触发时机 —— 在 touchend 事件响应 250ms 无操作后,触发singleTap。

因此,点击穿透的现象就容易理解了,在这 300ms 以内,因为上层元素隐藏或消失了,由于 click 事件的滞后性,同样位置的 DOM 元素触发了 click 事件(如果是 input 则触发了 focus 事件)。在代码中,给我们的感觉就是 target 发生了飘移。

How

理解点击穿透的原因,我们从各种途径去阻止现象的产生。

毫无疑问,能想到的方法很多,比如中间的层添加一个 300ms 渐隐的动画,触摸结束后阻止 click 事件等。

1. 触摸开始时 touchstart 事件触发时,preventDefault()。毫无疑问,很容易想到这一点,而且也从根本上解决了这个问题。但是,它有一个避免不了或者说引入了很大的缺陷,页面中DOM 元素无法再进行滚动了。这个方法显然不能满足我们的需求,但是这个思路其实可以给我们更多的启发,比如说 iscroll 只允许横向滚动的实现,相关实现这里暂且不表。

2. 触摸结束时 touchend 事件触发时,preventDefault()。看上去好像没有什么问题,但是,很遗憾的是不是所有的浏览器都支持。

3. 禁止页面缩放 通过设置meta标签,可以禁止页面缩放,部分浏览器不再需要等待 300ms,导致点击穿透。点击事件仍然会触发,但相对较快,所以 click 事件从某种意义上来说可以取代点击事件, 而代价是牺牲少数用户(click 事件触发仍然较慢)的体验。

<meta name="viewport" content="width=device-width, user-scalable=no">

IE 10可以用 CSS 取消点击穿透的延迟:

html {
    -ms-touch-action: manipulation;
    touch-action: manipulation;
}

IE 11+ 可以用 touch-action: manipulation; 属性来阻止元素的双击缩放。

4. CSS3 的方法 虽然主要讲的是事件,但是有必要介绍一个 CSS3 的属性 —— pointer-events。

pointer-events:  auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit;

pointer-events 属性有很多值,有用的主要是 auto 和 none,其他属性为 SVG 服务。

查看浏览器支持情况 可见移动端开发还是可以用的。

属性含义
auto默认值,鼠标或触屏事件不会穿透当前层
none元素不再是target,监听的元素变成了下层的元素(如果子元素设置成 auto,点击子元素会继续监听事件)

5. 处理点击事件 —— Touch to Click 最靠谱的方案还是从点击事件的根源上解决问题。用 js 去判断幽冥点击,然后阻止点击穿透。这种方式显然可以实现,缺点是阻止点击穿透时需要小心,不要导致原生的 HTML 元素(如:链接,多选框,单选框)无法正常运行。

通过上文中介绍的 touches,targetTouches,changedTouches 参数,我们可以构建出这样的测试页面,可以统计出点击穿透的时间,以及已经响应的情况。

 preventDefault()点击穿透时间点击穿透区域
 touchstarttouchend缩放页面禁止缩放页面缩放页面禁止缩放页面
Safari Mobile iOS 5.1.1YesYes370ms after end370msafter endtouchstarttouchstart
Safari Mobile iOS 6.1.3YesYes370ms after end370msafter endtouchstarttouchstart
Safari Mobile iOS 7.1.1YesYes370ms after end370msafter endtouchstarttouchstart
Android 2.3.7YesNo410ms after end410msafter endtouchstarttouchstart
Android 4.0.4YesNo300ms after end10ms after endtouchstarttouchstart
Android 4.1.2YesNo300ms after end300msafter endtouchstarttouchstart
Android 4.2.2YesNo300ms after start10ms after endtouchstarttouchend
IE10 Windows Phone 8NoNo310ms after end10ms after endtouchendtouchend
Blackberry 10YesYes260ms after end10ms after endtouchstarttouchstart
Chrome for iOSYesYes360ms after end360msafter endtouchstarttouchstart
Chrome for AndroidYesYes300ms after start10ms after endtouchstarttouchend
Firefox for AndroidYesNo300ms after end10ms after endtouchstarttouchend

由此可以看出: 1. 点击穿透受浏览器和页面是否缩放影响 2. 点击穿透有两种情况:快速情况有 10ms 慢速情况有 300ms 3. 在 touchend 时间上调用 preventDefault() 可以阻止多数情况的点击穿透

代码上处理建议如下:

  1. 在touchend事件上调用 preventDefault()
  2. 在一次成功的点击后,建议接下来的 500ms 以内取消所有的 click 事件。
  3. 分析点击事件,判断如果是慢速点击穿透,则取消所有 click 事件,如果是快速点击穿透,取消触摸事件 50ms以内的 click 事件即可。

有个好消息是,移动端开发已经有人写好相应的库,帮助我们处理点击穿透。 fastclick可以参考和使用。 其实现思路是,取消 click 事件(参看源码164-173行),用touchend 模拟 快速点击行为(参看源码 521-610 行)。



源引:http://liudong.me/web/touch-defect.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值