Skip to main content

倒计时组件

主要难点在于计时误差的校准。

为什么不直接用 setInterval

setInterval 把回调放进任务队列,若主线程繁忙(长任务、页面切到后台被节流),回调会被延迟甚至丢弃,误差会累积,导致倒计时越走越不准。

实现:setTimeout 自校准

setTimeout 模拟 setInterval,每次根据「应该到达的时间」与「实际时间」的差值动态修正下一次延时,误差不累积:

function countdown(duration, onTick, onEnd) {
const endTime = Date.now() + duration;
let timer = null;

const tick = () => {
const remain = endTime - Date.now();
if (remain <= 0) {
onTick(0);
onEnd?.();
return;
}
onTick(remain);
// 用对 1000 取余修正漂移,保证下一次在整秒边界附近触发
const next = remain % 1000 || 1000;
timer = setTimeout(tick, next);
};

tick();
return () => clearTimeout(timer); // 返回取消函数
}

误差校准(前端 vs 后端)

  • 前端时间不可信:用户可以改本机时间。抢购、秒杀类倒计时必须以服务器时间为准
  • 校准方案:请求一次后端时间,算出 diff = serverTime - clientTime,后续都用 Date.now() + diff 作为「当前服务器时间」来计算剩余时间。
  • 页面切后台setInterval 在后台被节流,回到前台用「目标结束时间 - 当前时间」重新计算剩余,而不是依赖累加,天然能纠正。

延伸

  • 抢购倒计时:以服务器时间为准 + 结束时拉取最新状态防本地误差。
  • 多个倒计时建议共用一个定时器统一驱动,减少 timer 数量。