准确跳动需用 requestAnimationFrame + new Date() 校准,或 performance.now() 补偿失焦;setInterval 因浏览器节流、主线程阻塞等导致秒数漂移、卡顿甚至跳秒。

直接用 setInterval 每 1000ms 更新一次,无法保证“准确跳动”——秒数会漂移、卡顿、甚至跳两下。 真正的准确跳动必须避开定时器累积误差,靠时间差驱动,且需处理页面失焦导致的定时器暂停问题。
为什么 setInterval(..., 1000) 会不准
浏览器在标签页失焦、系统休眠、CPU 负载高时会节流或暂停 setInterval,导致回调延迟执行。比如本该在 15:10:01.000 触发,结果拖到 15:10:01.980 才执行,下一次又延后……几秒后就明显“卡住再猛跳”。toLocaleTimeString() 或手动拼接也救不了这个底层机制问题。
- Chrome/Firefox 对后台标签页的
setInterval最低限制为 1000ms,实际可能更久 - 即使前台运行,JS 主线程阻塞(如长任务)也会让定时器排队等待
- 每次执行耗时哪怕只有 2ms,1000 次后就偏移 2 秒
用 requestAnimationFrame + 时间戳校准(推荐)
核心思路:不依赖固定间隔,而是每帧检查当前真实时间,只在秒数变化时更新 DOM。这样无论帧率高低、是否失焦,显示的永远是“此刻该显示的秒数”。
function updateClock() {
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
document.getElementById('clock').textContent = timeStr;
}
<p>function tick() {
updateClock();
requestAnimationFrame(tick);
}
tick();-
requestAnimationFrame在浏览器重绘前调用,天然与屏幕刷新同步,视觉更顺滑 - 不依赖
setTimeout/setInterval,规避了它们的节流和漂移缺陷 - 每次取
new Date(),确保显示的是系统当前真实时间,不是“推算出来”的时间 - 适合数字时钟;若要做指针动画,需额外用
now.getMilliseconds()插值计算角度
兼容旧浏览器?用 performance.now() 补偿失焦
IE10+ 和所有现代浏览器都支持 performance.now(),它返回高精度时间戳(毫秒级,带小数),且不受系统时间调整影响。结合上一次更新时间,可主动补偿失焦期间的跳秒:
立即学习“前端免费学习笔记(深入)”;
let lastUpdateTime = performance.now();
function updateWithCompensation() {
const now = performance.now();
const diff = now - lastUpdateTime;
if (diff >= 1000) {
const realNow = new Date();
document.getElementById('clock').textContent = realNow.toLocaleTimeString();
lastUpdateTime = now;
}
}
setInterval(updateWithCompensation, 200);- 用 200ms 频率轮询,既轻量又能及时捕获秒变点
-
performance.now()不受系统时间回拨/跳变影响,比Date.now()更稳 - 注意:Safari 旧版对
performance.now()支持不一致,生产环境建议加 try/catch 回退到Date.now()
最易被忽略的一点:DOM 更新本身有开销。如果 getElementById 查找频繁或样式触发重排,仍可能导致视觉卡顿。把目标元素缓存到变量里,用 textContent 替代 innerHTML,能省下关键几毫秒。



















