前言
为什么会出现这个呢,因为之前产品搞了一个倒计时,需要倒计时结束之后重新请求接口。但是在实际使用的时候发现一个问题----用户隐藏tab之后重新打开,会导致倒计时不准从而导致请求接口的时机出现延误。经过调研发现是定时器休眠导致的。所以进行了一次总结。
这里是之前的定时器代码 ---- 手把手带你撸一个精准倒计时。
遇到的问题
- 现在的浏览器为了极致的性能优化,对于定时器等功能都有一定的魔改。比如:当我们在
倒计时运行期间
因为某些事情隐藏浏览器
或者切换tab
的时候,浏览器会将定时器的执行周期延长到一分钟。这会导致倒计时功能的不精准。
Chrome 88后更新了更为激进的省电策略[[1]](https://www.zhihu.com/question/263914822#ref_1),导致后台非活动页面中setInterval、setTimeout回调的执行间隔拉长到1分钟以上。
解决方案: 如果是电脑端浏览器的话,可以监听页面的隐藏和显示,在判断用户重新打开浏览器的时候,进行接口调用,重置定时器事件。如果是定时器这类的推荐这种使用方法,可以保证定时器的准确性
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
// 添加监听器
document.addEventListener(visibilityChange, function() {
if(!document[hidden]){
console.log('在这里进行定时器事件的重置');
}
}, false);
- 在页面隐藏期间,我们所造成倒计时效果
处于等待阶段
,但是事件依然在生成堆积
,这就会导致一次大爆发
。当我们重新显示浏览器的时候,积攒的时间会一次性的爆发出来。这里最直观的体现在于弹幕系统
或者时钟系统
的动画。这些动画事件会在显示的时候进行大量更新。从而导致页面效果与预期的不符。
解决方案:
针对这种堆积事件,我们可以使用 setTimeout递归模拟setInterval
来解决,堆积的机制是因为setInterval在休眠的时候依然在不停的添加时间。如果我们使用setTimeout去模拟的话,就会避免这种问题,不会进行堆积。这里不同的浏览器的反应不同,大体有两种情况:第一种:隐藏tab也会继续执行setTimeout的递归
和 第二种: 隐藏tab之后会暂停事件执行和插入,等重新显示的时候继续执行。
// setTimeout递归
function getDate() {
getTimeOptions();
setTimeout(() => {
getDate();
}, 1000);
}
getDate();
其他的解决方案
使用播放视频的时候浏览器不休眠的机制 将以下代码添加到页面之中执行即可。不过对于Safari,该方法无效。
const chromeVersion = /Chrome\/([0-9.]+)/
.exec(window?.navigator?.userAgent)?.[1]
?.split('.')[0];
if (chromeVersion && parseInt(chromeVersion, 10) >= 88 && !isMobile()) {
const videoDom = document.createElement('video');
const hiddenCanvas = document.createElement('canvas');
videoDom.setAttribute('style', 'display:none');
videoDom.setAttribute('muted', '');
videoDom.muted = true;
videoDom.setAttribute('autoplay', '');
videoDom.autoplay = true;
videoDom.setAttribute('playsinline', '');
hiddenCanvas.setAttribute('style', 'display:none');
hiddenCanvas.setAttribute('width', '1');
hiddenCanvas.setAttribute('height', '1');
hiddenCanvas.getContext('2d')?.fillRect(0, 0, 1, 1);
videoDom.srcObject = hiddenCanvas?.captureStream();
}
使用webWoker
Web Workers 是 HTML5 提供的一个javascript多线程解决方案,可以将一些大计算量的代码交由web Worker运行而不冻结用户界面。 注意:Web Workers需要IE10及其以上的浏览器支持。