移动端网页点击延迟及事件穿透
到2024年,大多数现代移动浏览器(如 iOS Safari、Android Chrome 等)都移除了 300ms 延迟,前提是网页的 viewport
设置中禁止双击缩放
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
一、click事件的300毫秒延迟是怎么产生的?
苹果在2007年发布iphone前夕遇到一个问题,当时的网页都是针对大屏编写的,如果直接在iphone上浏览则会出现横向的滚动条,于是他们制定了一些规则来缩放网页,通过双击可以还原网页大小,这种方法后来被其它移动浏览器厂商所采用。
如何判断是单击还是双击呢?浏览器在接收到点击事件后,会等待 300ms,以判断用户是否在执行双击缩放 操作:
- 单击操作:在 300ms 内没有检测到第二次点击,触发点击事件。
- 双击缩放:在 300ms 内检测到第二次点击,执行页面缩放。
这种机制导致了点击事件在触发时存在 300ms 的延迟。
相应的解决办法
1.禁用缩放
<meta name="viewport" content="width=device-width, user-scalable=no">
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字。
<meta name="viewport" content="width=device-width">
为了让桌面站点能在移动端浏览器正常显示,移动端浏览器默认的视口宽度并不等于设备浏览器视窗宽度,而是要比设备浏览器视窗宽度大,通常是980px。我们可以通过上面的标签来设置视口宽度为设备宽度。那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。
3.css3的touch-action
touch-action
这个CSS属性。这个属性指定了相应元素上能够触发的浏览器的默认行为。文档说明
html { -ms-touch-action: manipulation; touch-action: manipulation; }
该方案只支持移动端的chromiun 和 iOS 9.3+
二、事件穿透
1.典型场景
页面上有一个弹层A,绑定了click事件,点击后打开一个B,B通常又是弹层或遮罩层,我们给B元素的touchstart或touchend绑定一个回调函数,该回调函数中会隐藏B元素。
当用户触摸B元素,B元素被隐藏后,B元素点击位置所在的下层元素会触发click事件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .mask{ width: 200px; height: 100px; background-color: gold; } .top{ width: 100px; height:100px; background-color: gray; position: absolute; top:0; left: 0; } </style> </head> <body> <div class="mask" id="bottom">我是底层</div> <div class="top" id="top">我是上层</div> <script> window.onload=function(){ document.getElementById("bottom").onclick=function(){ alert('我是底层'); } document.getElementById("top").ontouchend=function(){ this.style.display='none'; } } </script> </body> </html>
2.产生的条件
上层元素采用touchstart或touchend,下层元素有click事件或是一个指定了href的超链接,上层元素在触摸后不可见
3.产生的原因
在移动端浏览器中,点击一个元素后,执行的事件顺序是touchstart > touchend > click,touchend后会等待300毫秒,然后执行click事件。当touchend把上层元素隐藏之后,隔了300ms,浏览器执行click事件,但是此时上层元素不见了,所以该事件被派发到了底层元素身上。如果底层元素是一个链接,那此时页面就会意外地跳转。
4.如何解决
网上搜到的meta头禁用缩放,css3的touch-action这两种方法都是瞎扯蛋,经实验,可行的方法如下:
- 统一使用touch事件(
touchstart
、touchend),然后上层元素隐藏后使用event.preventDefault(),要不然下层元素如果是a标签,a标签的href也是click行为 - 如果不需要判断触摸手势等,可以把页面内所有的touch换成click
- 延迟法:给消失的元素做一个延迟消失的动画,设置动画duration大于300ms,这样当延迟的 click 触发时,上层元素还在,就不会“穿透”到下方的元素了。
- 下层元素使用css的pointer-events:none,当上层元素隐藏时将下层元素的pointer-events设为none,然后400ms后把该属性恢复成auto。(该方法的问题在于必须要知道可能受影响的下层元素有哪些,实际项目中意义不大)
window.onload=function(){ document.getElementById("bottom").onclick=function(){ alert('我是底层'); } document.getElementById("top").ontouchend=function(e){ this.style.cssText="display:none;" var bottom=document.getElementById("bottom"); bottom.style.cssText="pointer-events:none"; setTimeout(function(){ bottom.style.cssText="pointer-events:auto"; }, 400); } }