JavaScript实现精准毫秒秒级计时 解决setTimeout计时偏差问题
问题原因分析
- 原setTimeout版本是固定步进逻辑:每次执行固定将计时值+10ms(你代码中自定义的millisecond变量每加1对应10ms,满100进1秒),所以可以输出连续的两位毫秒序列,但是setTimeout本身受事件循环阻塞,长期运行会出现累计偏差。
- 改用Date.now()后的版本存在两个核心问题:
- 用
requestAnimationFrame调度渲染,帧率约60fps,每次调用间隔约16ms,远大于你需要的10ms步长,自然会跳过毫秒值 - 直接从Date.now()取时间快照,没有模拟步进逻辑,只会输出当前真实时间对应的毫秒值,不会补全中间跳过的序列
- 用
解决方法
核心思路是:用Date.now()计算真实累计时长作为基准,按照你需要的10ms步长模拟生成连续的时间序列,同时动态调整setTimeout调度间隔,避免累计漂移
修改后可直接运行的代码如下:
// 步长配置,10对应原逻辑的10ms步进,要1ms级输出就改为1 const STEP = 10; var paused = true, lastTickTime = 0, // 上次执行步进的真实时间戳 currentCount = 0; // 累计步进次数,单位和STEP对应 // 原格式化逻辑保留 function format(n) { return (n < 10 ? "0" + n : n); } function render() { // 从累计步进值计算完整时间 const totalMs = currentCount * STEP; const hour = format(Math.floor(totalMs / 3600000)); const min = format(Math.floor((totalMs % 3600000) / 60000)); const sec = format(Math.floor((totalMs % 60000) / 1000)); const ms = format(Math.floor((totalMs % 1000) / 10)); // 这里替换为你自己的输出逻辑,比如更新DOM或者打印 console.log(`${hour}:${min}:${sec}.${ms}`); } function runTick() { if (paused) return; const now = Date.now(); // 计算上次执行到现在需要补的步数,避免跳号 const delta = now - lastTickTime; const steps = Math.floor(delta / STEP); if (steps > 0) { currentCount += steps; // 对齐步长时间,消除误差 lastTickTime = now - (delta % STEP); render(); } // 动态计算下一次调度的间隔,保证刚好在步长点触发 const nextDelay = STEP - (Date.now() - lastTickTime); setTimeout(runTick, nextDelay); } function startStopwatch(evt) { if (paused) { paused = false; lastTickTime = Date.now(); runTick(); } } function stopStopwatch(evt) { if (!paused) { paused = true; } } function resetStopwatch(evt) { currentCount = 0; lastTickTime = Date.now(); render(); }
该方案既保留了连续的步进输出效果,又用真实时间校准,不会出现传统setTimeout计时的累计偏差问题。
内容的提问来源于stack exchange,提问作者Ruan Matt
相关产品推荐
相关产品推荐

