标签页切到后台,我的倒计时就不准了:用时间戳校正计时器

举报
yd_221565968 发表于 2026/11/02 00:46:46 2026/11/02
【摘要】 番茄钟切到后台后倒计时变慢,根因是用 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)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。