• setInterval倒计时切换页面后不准


    背景

    最近在做一个倒计时时,发现当切换浏览器tab后,再切回倒计时页面,倒计时的数据不准,比真正的剩余时间多,短时间还好,时间长了,计时器的误差会很大。

    原因

    倒计时是用setInterval每1000毫秒触发一次。

    在进入页面时,计算剩余时间,把剩余时间用setInterval每1000毫秒触发一次进行减法。

    但是由于浏览器的优化机制,为了更极致的优化,在切换tab之后浏览器会把setInterval的执行效率降低,在浏览器窗口非激活的状态下会停止工作或者以极慢的速度工作。那么这时候就不是1000毫秒减一次了,所以会有误差。

    用setInterval实现计时

    var start = new Date().getTime(), count = 0;
    var interval = setInterval(function () {
        count++
        console.log(new Date().getTime() - (start + count * 1000) + 'ms')
        if(count == 10){
        clearInterval(interval);
        }
    }, 1000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image.png

    可以看到,我打印的new Date().getTime() - (start + count * 1000) ,
    也就是每次计时的误差,理想情况下,应该是0。

    用setTimeout实现计时

    var start = new Date().getTime(), count = 0,interval = 1000;
    var timer = setTimeout(doFunc,interval);
    function doFunc(){
        count++
        console.log(new Date().getTime() - (start + count * 1000) + 'ms');
      if(count < 10){
    	    timer = setTimeout(doFunc,interval);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image.png

    也是一样的会出现误差

    setInterval、setTimeout误差的不同之处

    setInterval指定的是“开始执行”之间的间隔,并不考虑每次任务执行本身所消耗的时间。因此实际上,两次执行之间的间隔会小于指定的时间。比如,setInterval指定每 100ms 执行一次,每次执行需要 5ms,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始。

    为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。

    模拟阻塞事件

    setInterval
    //阻塞代码
    setInterval(function () {
      var n = 0
      while (n++ < 1000000000);
    }, 1000)
    
    var start = new Date().getTime(), count = 0;
    var interval = setInterval(function () {
        count++
        console.log(new Date().getTime() - (start + count * 1000) + 'ms')
        if(count == 10){
        clearInterval(interval);
        }
    }, 1000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    image.png

    setTimeout
    //阻塞代码
    setInterval(function () {
      var n = 0
      while (n++ < 1000000000);
    }, 1000)
    
    var start = new Date().getTime(), count = 0,interval = 1000;
    var timer = setTimeout(doFunc,interval);
    function doFunc(){
        count++
        console.log(new Date().getTime() - (start + count * 1000) + 'ms');
      if(count < 10){
    	    timer = setTimeout(doFunc,interval);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image.png

    可以看到加了一些阻塞线程的代码后,误差越来越严重,
    在实际项目中,执行计时器的同时,会有很多其他异步阻塞事件,会导致倒计时功能不精确。

    解决方案

    1、setInterval每次触发的时候,重新计算剩余时间(误差在一分钟以内)

    2、Web Workers(这个在nuxt中引入会报错,涉及到webpack改动较大,暂时不用)

    3、进行误差修正,也就是获取到误差的值,并且根据这个误差值来动态调整我们执行回调的间隔时间

    • 计算误差值
    • 动态调整执行setTimeout的间隔

    加上动态误差修正

    var start = new Date().getTime(), count = 0,interval = 1000;
    var offset = 0;//误差时间
    var nextTime = interval - offset;//原本间隔时间 - 误差时间
    var timer = setTimeout(doFunc,nextTime);
    function doFunc(){
        count++
        console.log(new Date().getTime() - (start + count * interval) + 'ms');
         offset = new Date().getTime() - (start + count * interval);
        nextTime = interval - offset;
        if (nextTime < 0) { nextTime = 0; }
      if(count < 10){
    	    timer = setTimeout(doFunc,nextTime);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    试试效果:

    image.png

    把每次的nextTime打印出来看看:

    image.png

    以看到每次的nextTime都会根据上次的误差值来动态调整,以尽量减少整体的误差。

  • 相关阅读:
    内存泄露检测工具VLD(Visual Leak Detector)使用说明
    TCP&UDP
    100 余个网页设计优化案例(用户体验、交互优化等方面)
    逻辑漏洞——业务逻辑问题
    泡咖啡问题
    批发行业进销存-webview 读取NFC,会员卡 源码CyberWinApp-SAAS 本地化及未来之窗行业应用跨平台架构
    一年一度的中秋节马上又要到了,给你的浏览器也来点氛围感吧
    zabbix监控
    基于Abaqus-Simpack联合仿真车辆-浮置板轨道耦合动力学仿真
    通过 DevOps、CI/CD 和容器增强您的软件开发之旅...
  • 原文地址:https://blog.csdn.net/weixin_45658814/article/details/132902082