先看一下测试代码



<body>
  <div id="delay">click有延迟 </div>
  <div id="no-delay">touchstart无延迟</div>
  <div> <a id="link1" href="#1">链接1</a> <a id="link2" href="#2">链接1</a></div>
  <div id="log"></div> 
  <!-- 方便手机调试 -->

  <style>
    body {
      font-size: 60px;
    }
  </style>

  <script>
    const $ = s => document.querySelector(s)
    const log = str => $('#log').innerText = str 

    let t1, t2

    $('#delay').ontouchstart = e => { //记录时间
      t1 = Date.now()
    }

    $('#delay').onclick = e => { //先触发ontouchstart,再 ontouchend,最后触发 click 
      log(Date.now() - t1) //click 执行时,和 ontouchstart 差多少时间,用 log 展示到页面上
    }

    $('#no-delay').ontouchstart = e => {
       log('touchstart无延迟') //ontouchstart 直接输出
    }

    $('#link1').ontouchstart = e => {
      t2 = Date.now() //点链接1时,hash 会切换,
    }

    $('#link2').ontouchstart = e => {
      t2 = Date.now() //点链接2时,hash 会切换,
    }

    window.onhashchange = () => {  //调用 onhashchange ,现在的时间减去触摸的时间
      log(`link ${location.hash} : ${Date.now() - t2}ms`)
    }
  </script>
</body>



点击 '#delay' 元素时,首先触发的是 ontouchstart ,再触发 onclick,中间是有大约 300ms 的延迟时间




writeFileSync时间过长 writefile function failed_延迟时间


同样,点击 '#link1' 元素时,url 的变化,也是有 300ms 的延迟时间


writeFileSync时间过长 writefile function failed_延迟时间_02


这就是所谓的在移动端有 300ms 延迟(在手机端可能会是 400ms 这个不确定)

延迟的原因

以前的网页全都是适配的 PC 浏览器,全都是给 PC 看的,这个页面放到手机上,用起来就会不方便,为了方便用户快速的去缩放页面,Iphone 引入了双击缩放功能,提高用户体验,但是问题来了,当我在屏幕上点击一个元素时,浏览器怎么知道我是单击还是双击,它会立刻跳转去响应呢,还是等着双击,所以此时浏览器只能等,在300ms 以内,如果没有下一次点击,浏览器认为是点击不是双击,不需要页面缩放,去响应点击事件,在 300ms 以内,又点击了一次,表示此操作是双击(而不是跳转或事件单击),做了页面缩放,浏览器必须等 300ms ,才能知道用户的真实行为,对于普通的事件响应,只有 300ms 后才知道,所以延时 300ms , 其他设备浏览器也开始模仿 iphone(iphone是无键盘全触屏手机的始祖)

现在是 9102 年了,大部分浏览器都没有此设置了,也就没有延迟的问题了。不过还是可以了解下具体的解决方案。

如何解决呢

方法1:在 ios android 都会生效,必须 width = device-width ,若设置为 width = 800px , android 生效,ios 不生效,所以不能写其他值


<meta name="viewport" content="width=device-width">


延迟会消失(其实没有消失,只不过延迟时间变短了)


writeFileSync时间过长 writefile function failed_延迟时间_03


为什么还会有 73ms 的延迟呢

正常情况下,用户点击 'click 有延迟' 时,再松开,是有时间的(可能几十毫秒),会触发 touchstart , touchmove, touchend, touchstart 和 touchend 之间是有时差的,touchend 之后,click 事件才会触发。这个时间不是设备故意设置的 300ms 了,

方法2:使用 fastclick 库,或者不用click用touchstart

若使用 touchstart ,可能拖拽缩放是有问题的,比如手指一触碰就会响应... 而且没有按下的效果(体验差)

fastclick 的使用及原理

  • 若定义meta 时,width 是其他值时,还是存在延迟问题
  • 2017年以前,很多机型不支持 meta

所以此时需要 fastclick

如何使用

引入


<script src="https://cdn.bootcss.com/fastclick/1.0.6/fastclick.js"></script>


然后加上


<script>
    document.addEventListener('DOMContentLoaded', function () {
      FastClick.attach(document.body)
    }, false)
  </script>


原理

正常情况下,用户 touchstart touchmove touchend 之后,300ms 后,会触发 click 事件

在检测到 touchend 事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,然后立即去响应,并把浏览器在300ms之后真正的click事件阻止掉

具体实现


const FastClick = (function () {

  function attach(root) {
    let targetElement = null
    root.addEventListener('touchstart', function () {
      targetElement = event.target
    })
    root.addEventListener('touchend', function (event) {
      event.preventDefault()
      let touch = event.changedTouches[0]
      let clickEvent = document.createEvent('MouseEvents')
      clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null)
      clickEvent.forwardedTouchEvent = true
      targetElement.dispatchEvent(clickEvent)
    })
  }

  return { attach }
})()

FastClick.attach(document.body)