setInterval倒计时切换页面后不准

背景

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

原因

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

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

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

用setInterval实现计时

javascript 复制代码
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)

可以看到,我打印的new Date().getTime() - (start + count * 1000) ,

也就是每次计时的误差,理想情况下,应该是0。

用setTimeout实现计时

javascript 复制代码
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);
    }
}

也是一样的会出现误差

setInterval、setTimeout误差的不同之处

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

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

模拟阻塞事件

setInterval
javascript 复制代码
//阻塞代码
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)
setTimeout
javascript 复制代码
//阻塞代码
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、setInterval每次触发的时候,重新计算剩余时间(误差在一分钟以内)

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

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

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

加上动态误差修正

javascript 复制代码
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);
    }
}

试试效果:

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

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

相关推荐
Halo_tjn19 分钟前
虚拟机相关实验概述
java·开发语言·windows·计算机
star _chen22 分钟前
C++实现完美洗牌算法
开发语言·c++·算法
周杰伦fans29 分钟前
pycharm之gitignore设置
开发语言·python·pycharm
招来红月1 小时前
记录JS 实用API
javascript
别叫我->学废了->lol在线等1 小时前
演示 hasattr 和 ** 解包操作符
开发语言·前端·python
霍夫曼1 小时前
UTC时间与本地时间转换问题
java·linux·服务器·前端·javascript
VX:Fegn08952 小时前
计算机毕业设计|基于Java人力资源管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·课程设计
DARLING Zero two♡2 小时前
浏览器里跑 AI 语音转写?Whisper Web + cpolar让本地服务跑遍全网
前端·人工智能·whisper
꒰ঌ小武໒꒱2 小时前
文件上传全维度知识体系:从基础原理到高级优化
javascript·node.js
Lovely Ruby2 小时前
前端er Go-Frame 的学习笔记:实现 to-do 功能(三),用 docker 封装成镜像,并且同时启动前后端数据库服务
前端·学习·golang