标签页切到后台,我的倒计时就不准了:用时间戳校正计时器
【摘要】 番茄钟切到后台后倒计时变慢,根因是用 setInterval 累减计时,而浏览器会节流后台定时器。本文改用结束时间戳反算剩余时间,并给出暂停恢复、可见性刷新、跨刷新恢复等处理方式。
番茄钟做完自测时一切正常,直到我切去别的标签页写作业,二十五分钟后回来一看:倒计时还剩三分多钟。计时器“变慢”了。这篇记录一下原因和改法。
一、问题出在哪
最初的实现是最直觉的写法:每秒减一。
let left = 25 * 60;
setInterval(() => {
left--; // 假设每次回调间隔恰好 1 秒
render(left);
}, 1000);
它默认 setInterval 每次回调之间恰好相隔 1 秒,但这个前提并不成立:
- 后台节流:标签页不可见时,浏览器会把定时器的最小间隔压到 1 秒甚至更长;进入省电状态后间隔可能被拉到几十秒一次。
- 回调排队:setInterval 只保证“不早于”间隔触发。主线程被长任务占住时,回调会被推迟。
- 误差累积:每次少走的那几十毫秒会一直攒下去,时间越久偏得越多。
所以用“回调次数”计时,本质上是把一件不可靠的事当成了时钟。
二、改法:记终点,不记次数
不再累减,而是在开始时记下结束时间戳,每次回调都用当前时间重新算剩余量:
let endAt = null; // 计时结束的时间戳
let timer = null;
function start(seconds) {
endAt = Date.now() + seconds * 1000;
tick();
timer = setInterval(tick, 500); // 跑快一点,保证秒数跳变及时
}
function tick() {
const left = Math.max(0, Math.round((endAt - Date.now()) / 1000));
render(left);
if (left === 0) {
clearInterval(timer);
timer = null;
onFinish();
}
}
这样无论中间漏掉多少次回调、每次延迟多久,显示的剩余时间都由两个时间戳之差决定,不会偏移。回调频率只影响画面刷新是否及时,不影响计时准确度,所以我把间隔设成 500 毫秒,避免秒数跳变看起来卡顿。
三、暂停与继续
改用时间戳后,暂停不能只是停掉定时器,否则恢复时终点已经成了过去。做法是暂停时存下剩余毫秒,继续时用它重新算终点:
let pausedLeft = null;
function pause() {
pausedLeft = endAt - Date.now(); // 存下剩余毫秒
clearInterval(timer);
timer = null;
}
function resume() {
endAt = Date.now() + pausedLeft; // 用剩余毫秒重建终点
pausedLeft = null;
tick();
timer = setInterval(tick, 500);
}
四、回到前台立刻刷新
即使计时准确,从后台切回来的瞬间,画面可能还停留在几十秒前的数字,要等下一次回调才更新。监听可见性变化,回来时主动跑一次即可:
document.addEventListener("visibilitychange", () => {
if (!document.hidden && timer) tick(); // 回到前台立刻刷新一次
});
五、几个注意点
- 别用 performance.now() 跨休眠计时:它在部分系统休眠期间不走,而番茄钟要的是真实流逝时间,Date.now() 更合适。
- 用户改系统时间会影响结果:这是 Date.now() 的固有代价。对番茄钟这种场景可以接受,若要防范,可在每次 tick 时检查剩余量是否出现异常跳变。
- 后台无法准点提示:节流只是让回调变稀疏,结束时刻的提示音可能迟到几十秒。想准点通知,需要配合 Notification API 或 Service Worker。
- 刷新页面也能恢复:把 endAt 存进 localStorage,重新打开时拿它和当前时间相减,计时就能接着走。
六、小结
凡是和时间有关的功能,都不要依赖“定时器被调用了多少次”,而应该依赖“现在距离目标时刻还有多久”。定时器只负责催促刷新,时间本身交给时间戳。进度条、限时答题、冷却倒计时都适用同一条原则。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)