移动端网页点击延迟及事件穿透

到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的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字。
 
2.更改默认的视口宽度
<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);
    }
  }
posted @ 2018-06-05 21:24  我是格鲁特  阅读(304)  评论(0)    收藏  举报