在线计时器

正计时与累计计时:从你设定的累计起点开始往前跑,显示值由 performance.now() 单调时钟相减得出,不按刷新次数累加,暂停、继续、重置都不丢时间,还能随时计次。

01:02:05.00
页面打开即以 3725000 毫秒(01:02:05.000)为累计起点自动开始正计时。
累计起点01:02:05.00
刷新间隔100 毫秒
已刷新次数0 次
按次数累加会少算0.0 毫秒
圈号本圈用时累计用时备注
100:00:12.5001:02:17.50演示数据
200:00:12.0001:02:29.50演示数据
300:00:12.5001:02:42.00演示数据
400:00:12.0001:02:54.00演示数据
500:00:12.5001:03:06.50演示数据
600:00:12.0001:03:18.50演示数据
700:00:12.5001:03:31.00演示数据
800:00:12.0001:03:43.00演示数据
900:00:12.5001:03:55.50演示数据
1000:00:12.0001:04:07.50演示数据
1100:00:12.5001:04:20.00演示数据
1200:00:12.0001:04:32.00演示数据

提示:打开页面就自动开始正计时,起点是输入框里的 3725000 毫秒。快捷键——空格切换开始与暂停、L 计次、R 重置(光标在输入框里时快捷键不生效)。表里那 18 行是演示数据,点一次「计次」就会清掉换成你自己的记录。

使用方法

  1. 「累计起始时长」单位是毫秒,默认 3725000,也就是 01:02:05.000。纯正计时填 0;要把之前已经计过的时长接着往下累计,就把那个数值换算成毫秒填进来。允许 0~359999999 的整数。
  2. 「刷新间隔」默认 100 毫秒,允许 50~1000。它只决定大字区多久重绘一次,不参与时间计算——改这个值不会让计时变快或变慢,只会让末两位数字跳得更密或更疏。
  3. 「计次备注前缀」默认 第 # 圈,里面的 # 会被替换成圈号;留空则备注列显示一个短横。
  4. 点「开始」往前走,「暂停」冻结当前用时,「继续」从冻结处接着跑,「重置」回到输入框里的累计起点并清空计次。打开页面时工具已经替你按下了「开始」。
  5. 键盘也能操作:空格切换开始与暂停、L 计次、R 重置。光标停在输入框里时这些键不生效,避免打字时误触。
  6. 计次表默认显示 12 行演示数据,点「加载更多行」展开余下 6 行;「复制计次表」以制表符分隔复制,「导出 CSV」写成带 BOM 的文件,Excel 打开不乱码。

这个在线计时器能解决什么问题

一是把已经跑掉的时长续上来。多数计时工具只能从 00:00:00 起算,一旦页面刷新、换了设备或者中途换人接手,前面那段就白计了。这就是累计计时的用法:「累计起始时长」填前一段已经计过的 3725000 毫秒,接着往下走就行。

二是长时间挂着也不走形。脚本按 performance.now() 相减显示用时,而不是每刷新一次就加 100 毫秒,所以连续跑一小时、中途切到别的标签页再回来,数字都对得上真实经过的时间。

三是一边跑一边分段记录。点「计次」会记下本圈用时与累计用时两列:前者是距上一次计次的间隔,后者是从起点算起的总时长。做实验记录、装配节拍、会议分段发言都能直接用。

四是把「为什么别人的计时器越跑越慢」讲清楚。下面给出 setInterval 漂移的模型和一次本机实测数据,你可以拿自己电脑上的表现对照。

本页不做这些事:不做从设定时长往回走的计时、不做固定周期自动换挡的循环、不做到点响铃提醒。它只做一件事——把从起点到现在经过了多少时间,如实显示出来。

为什么用 performance.now() 而不是 Date.now():单调时钟与 setInterval 漂移

这个在线计时器里只有一条时间公式:已用 = performance.now() - 基准时刻。performance.now() 由 W3C 的 High Resolution Time Level 2 规范定义,返回从页面导航开始(timeOrigin)到现在的毫秒数,是带小数的浮点数。按下「开始」时记下 基准时刻 = performance.now() - 起始时长,之后每次重绘都重新做这一减法,而不是在旧值上加一个间隔。

它是单调时钟,不受系统改时间影响。Date.now() 返回的是自 1970-01-01T00:00:00Z 起的墙钟毫秒,NTP 校时、用户手动改表、夏令时切换都会让它跳变;跳一下,用 Date.now() 相减的计时器就会瞬间多走或少走几秒甚至几小时。performance.now() 只朝一个方向增长,与墙上的时间无关,因此只适合量「经过了多久」,不适合对齐某个绝对时刻。这条取舍正好与本页「只做正计时」的定位一致。

真正会让计时器变慢的是「按次数累加」。常见写法是 setInterval(function () { el += 100 }, 100),把「回调触发一次」等同于「过去了 100 毫秒」。但规范只保证间隔不小于 100 毫秒,回调排队、样式重算、垃圾回收都会插在中间。设每次回调平均多花 d 毫秒,真实周期就是 100 + d,墙钟走过 T 毫秒后只触发 floor(T / (100 + d)) 次,累加式的亏欠是 T × d / (100 + d),随时间线性增长:d 取 1 毫秒时 1 小时少算 35643.6 毫秒(约 35.6 秒),8 小时少算 285148.5 毫秒(约 4 分 45 秒)。间隔越大这个比例越小:同样 d 取 1 毫秒,1000 毫秒间隔跑 1 小时只少算 3596.4 毫秒——但那是拿「秒针一跳」的粗糙显示换来的。

这不是纸上推算,本机实测对得上。在 Windows 上以 100 毫秒间隔用 setInterval 跑 20 秒,理论应触发 200 次,实测只有 183 次:真实周期 109.36 毫秒,单次平均晚 9.36 毫秒,累加式在这 20 秒里就少走了 1712.7 毫秒。109.375 毫秒恰好等于 15.625 × 7,而 15.625 毫秒正是 Windows 默认定时器分辨率——也就是说 100 毫秒的请求被对齐到了 7 个时间片上。用时间戳相减则完全不受这件事影响:晚触发只会让数字晚几十毫秒才跳,走出来的值仍然准确。

后台标签页会把这笔账放大几十倍。标签页隐藏后,Chrome 把 setInterval 压到最快 1000 毫秒一次;隐藏满 5 分钟进入 intensive throttling,最多每分钟一次。累加式因此断崖式欠账:按「前 5 分钟每秒 1 次、之后每分钟 1 次」估算,隐藏 30 分钟只触发 325 次,显示停在 00:00:32.50,而墙钟已经走了 00:30:00.00。本页的做法是:隐藏时直接 clearInterval,回到前台先按 performance.now() 重算一次再重建定时器,所以离开多久都不影响结果。可见性判据用 W3C Page Visibility 规范定义的 document.hidden 与 visibilitychange 事件,比监听窗口的 blur / focus 更可靠——切换应用、锁屏、最小化都会正确改变可见性。

完整示例(数值仅作演示)

这个在线计时器的示例条件与页面打开时的默认值完全一致:累计起始时长 3725000 毫秒、刷新间隔 100 毫秒。

刷新间隔与显示精度的对照表

下表按 T × d /(间隔 + d) 逐行算出,d 取 1 毫秒,即每次回调平均多花 1 毫秒;「累加式少算」是同一段时长下,用触发次数乘间隔的写法会比真实时间少算多少。

时长毫秒大字区显示100 毫秒间隔·累加式少算1000 毫秒间隔·累加式少算
100 毫秒10000:00:00.101.0 毫秒0.1 毫秒
1 秒100000:00:01.009.9 毫秒1.0 毫秒
5 秒500000:00:05.0049.5 毫秒5.0 毫秒
10 秒1000000:00:10.0099.0 毫秒10.0 毫秒
30 秒3000000:00:30.00297.0 毫秒30.0 毫秒
1 分钟6000000:01:00.00594.1 毫秒59.9 毫秒
2 分钟12000000:02:00.001188.1 毫秒119.9 毫秒
5 分钟30000000:05:00.002970.3 毫秒299.7 毫秒
10 分钟60000000:10:00.005940.6 毫秒599.4 毫秒
15 分钟90000000:15:00.008910.9 毫秒899.1 毫秒
30 分钟180000000:30:00.0017821.8 毫秒1798.2 毫秒
1 小时360000001:00:00.0035643.6 毫秒3596.4 毫秒
2 小时720000002:00:00.0071287.1 毫秒7192.8 毫秒
4 小时1440000004:00:00.00142574.3 毫秒14385.6 毫秒
8 小时2880000008:00:00.00285148.5 毫秒28771.2 毫秒
12 小时4320000012:00:00.00427722.8 毫秒43156.8 毫秒
24 小时8640000024:00:00.00855445.5 毫秒86313.7 毫秒
99 小时35640000099:00:00.003528712.9 毫秒356044.0 毫秒

把两列放在一起看就明白了:间隔越小,按次数累加的亏欠越大。100 毫秒间隔跑 1 小时少算 35.6 秒,1000 毫秒间隔只少算 3.6 秒——因为 d 固定为 1 毫秒,间隔越小它占的比例越高。想靠加大间隔来减少亏欠,代价是秒位数字跳得越来越粗;而按时间戳相减的写法根本没有这一列。

输入范围、边界与精度说明

常见问题

为什么不用 Date.now() 计时?

Date.now() 读的是墙钟。系统自动对时、你手动改时间、或者处在夏令时切换的时区,它都可能突然跳一大截,用它做减法的计时器会跟着跳。performance.now() 是单调时钟,只朝一个方向走,量「经过多久」不受影响——代价是它不能对齐墙上的某个时刻。

把刷新间隔改成 1000 毫秒,计时会变慢吗?

不会。刷新间隔只决定大字区多久重绘一次,用时始终由 performance.now() 与基准时刻相减得出,改成 1000 毫秒只是让数字一秒跳一次。反过来说,如果计时值是按「刷新一次加一个间隔」算的,改这个值才会改变结果——本页不是那种写法。

切到别的标签页,计时会停吗?回来后还准吗?

不会停,也不会少走。页面隐藏时本页主动清掉定时器、不在后台空转,回到前台先按 performance.now() 重算一次再重建定时器,所以离开多久都不影响累计用时。

「累计起始时长」是干什么用的?

用来把之前已经计过的时长续上。比如上一段计到 01:02:05 时被打断,把 3725000 毫秒填进来,新一段就从 01:02:05.00 接着往下走,而不是从零开始。填 0 就是纯正计时。

计次表里「本圈用时」和「累计用时」有什么区别?

「累计用时」是从起点算起的总时长,「本圈用时」是距上一次计次的间隔。第一次计次时,本圈用时等于当前用时减掉累计起点,所以示例里第 1 圈是 00:00:12.50 而不是 01:02:17.50。

为什么显示的最后两位是厘秒而不是毫秒?

厘秒就是 10 毫秒,是计时显示的通行档位:既能看出「在走」,又不会因为末位每秒变一百次而闪成一团。内部仍按毫秒保存,暂停再继续不会丢这部分精度。

键盘快捷键为什么有时候没反应?

因为光标停在输入框里。空格、L、R 在不聚焦输入控件时才生效,否则你填累计起点时按空格会被当成快捷键。想用快捷键先点一下页面空白处,或按 Tab 把焦点移出输入框。

数据会上传吗?刷新后记录还在吗?

不会上传,全部运算都在浏览器里完成,没有任何网络请求。但本页也不做本地存储:刷新后计时按输入框里的累计起点重新开始,计次表回到演示数据。要留下记录请先「复制计次表」或「导出 CSV」。

相关工具

需要从设定时长往回走(倒计时)、需要到点反复响铃的场景,本页不承担。