在线秒表

按下计次就记一圈:本圈用时由「这一次累计用时减上一次累计用时」得出,底层是 performance.now() 单调时钟,毫秒读数不受系统改时间影响;累计、分段差、与目标差同时给出。

00:13:29.910
未开始:大字区停在演示数据的合计 00:13:29.910,点「开始」会清空演示并从零记录。
圈数18 圈
最快圈00:00:44.210
最慢圈00:00:45.990
平均圈00:00:44.995
圈号本圈用时累计用时与上圈差(毫秒)与目标差(毫秒)
100:00:44.89000:00:44.890—-110
200:00:44.21000:01:29.100-680-790
300:00:45.35000:02:14.450+1140+350
400:00:44.68000:02:59.130-670-320
500:00:45.99000:03:45.120+1310+990
600:00:44.42000:04:29.540-1570-580
700:00:45.11000:05:14.650+690+110
800:00:44.93000:05:59.580-180-70
900:00:44.65000:06:44.230-280-350
1000:00:45.72000:07:29.950+1070+720
1100:00:44.38000:08:14.330-1340-620
1200:00:45.26000:08:59.590+880+260

提示:打开页面时表里是 18 圈演示数据(12 行可见、6 行折叠),合计 00:13:29.910;点「开始」会清空演示换成本次记录。快捷键——空格切换开始与暂停、L 计次、R 重置,在任意输入框按 Enter 套用目标单圈与小数位。

使用方法

  1. 页面打开时计次表已经放好 18 圈演示数据,大字区停在合计 00:13:29.910,也就是 809910 毫秒。点「开始」会先清掉演示行,从 00:00:00.000 重新记录。
  2. 「目标单圈时长」单位是毫秒,默认 45000,即 45.000 秒。它只参与最后一列「与目标差」和统计卡里的对比,不参与计时;改成别的值不会让秒表走快或走慢。
  3. 「显示小数位」默认 3,也就是毫秒;填 2 是厘秒、1 是分秒的十分位、0 只到整秒。这个设置只改显示格子,内部始终按毫秒双精度保存。
  4. 「开始」起跑,「计次」记一圈,「暂停」冻结当前用时,「重置」清空并恢复演示数据。「载入示例」等价于把 18 圈演示数据重新摆回来。
  5. 每按一次「计次」就追加一行五列:本圈用时、累计用时、与上圈差、与目标差,圈号自动递增。表内默认展示 12 行,余下 6 行点「加载更多行」展开。
  6. 键盘也能操作:空格切换开始与暂停、L 计次、R 重置;光标停在输入框里时这些键不生效,按 Enter 则用来套用输入框里的目标值与小数位。
  7. 「复制计次表」按制表符分隔复制五列,「导出 CSV」写成带 BOM 的 UTF-8 文件,Excel 打开不乱码。

这个在线秒表能解决什么问题

一是把「这一圈多久」和「到目前一共多久」分开看。跑步间歇、游泳分道、装配工位、实验取样都需要两个数:本圈用时告诉你这一段的配速,累计用时告诉你整场还剩多少力气。表里五列一次给全,不用在两行数字之间做心算。

二是把「比我快还是比我慢」量化出来。填一个目标单圈(默认 45000 毫秒),每一行的最后一列就是这一圈相对目标的毫秒差,正数慢、负数快;「与上圈差」则是相邻两圈的对比。看趋势比看绝对值有用得多——连续三圈都是正数,说明掉速了。

三是长时间挂着也不走形。这个在线秒表只记一次基准时刻,之后每次都用当前读数减基准,而不是每刷新一帧就往旧值上加一帧的时长;跑十几分钟、中途切走再回来,数字都对得上真实经过的时间。

四是把「毫秒位到底有多真」讲清楚。下面会说明浏览器怎样刻意把高精度时钟调粗、为什么 3 位小数不等于 3 位可信,以及手动点击的真实误差落在哪个量级。

本页不做这些事:不做从设定时长往回走的计时、不做固定周期自动换挡的循环、不做到点响铃提醒、不做多路同时计时。它只做一件事——把每一次点击的时刻如实记下来,再换算成你能读的时间。

分段时间怎么算:两次累计快照相减与 performance.now() 单调时钟

这个在线秒表内部只有一个时钟源:performance.now()。W3C 的 High Resolution Time Level 2 规定它返回从页面导航起点(performance.timeOrigin)到现在的毫秒数,是带小数的双精度浮点数,而且要求单调不减——同一页里后一次读到的值不会比前一次小。它和 Date.now() 是两套时钟:后者读的是墙上时间(自 1970-01-01T00:00:00Z 起的 UTC 毫秒),NTP 校时、手动改表、时区与夏令时切换都会让它跳变,拿它做减法来量「经过多久」,跳一下就会多走或少走几秒。

按下「开始」时只记一个数:基准 = performance.now()。之后每次重绘都重新做 已用 = performance.now() - 基准,不在旧值上加一帧的时长。这是秒表准不准的分水岭:requestAnimationFrame 在 60 Hz 屏幕上大约每 16.667 毫秒回调一次,但样式重算、垃圾回收、系统调度都会让回调晚到;如果按「每帧加 16.667 毫秒」累加,跑满 13 分钟的示例(约 4.8 万帧)平均每帧只要晚 0.1 毫秒,就会少算约 4.8 秒。按时间戳相减则没有这一项,晚到的回调只会让画面晚一点跳,读出来的值仍然准确。

分段时间用两次累计快照相减得到。每按一次「计次」只调用一次 performance.now(),把当时的累计毫秒推进 snaps 数组;第 i 圈的分段用时就是 snaps[i] - snaps[i-1],第 1 圈减 0。整场 18 圈只采样 19 次,而「每圈单独重开一个计时」的写法要点 36 次(每次点击既停旧的又起新的)。采样次数减半,意味着落在浏览器降精度区间的读数减半。演示数据可以自校验:44890 + 44210 + 45350 + … + 45640 = 809910 毫秒,与最后一圈的累计快照 809910 完全相等。

浏览器会刻意把 performance.now() 调粗,这是防侧信道攻击的措施:攻击者靠超高精度时间戳能推断缓存命中、猜测同机其它进程的活动。Chrome 默认把分辨率限制到 100 微秒(0.1 毫秒),只有页面开启跨源隔离(响应头带 Cross-Origin-Opener-Policy: same-origin 与 Cross-Origin-Embedder-Policy: require-corp)后才放宽到 5 微秒;Firefox 的默认隐私降精度更粗,量级在 1 毫秒。所以「显示 3 位小数」不等于「3 位都可信」,换个浏览器末位可能只在 0 附近跳。反过来说,用 0.1 毫秒的分辨率去量 45 秒的一圈,相对误差只有 2.22×10⁻⁴ %,对跑步、实验、装配节拍完全够用。另外,手动点击的真实精度受限于人的反应,落在百毫秒量级,毫秒位只在「同一次点击之后的程序内换算」里才有意义。

后台标签页不会让累计值缩水。requestAnimationFrame 在页面不可见时完全停止回调。本页的画面刷新走的正是它:页面一隐藏,回调自然停;回到前台的第一帧再算一次 performance.now() - 基准,中间离开多久都不影响结果。判据用 W3C Page Visibility 规范定义的 document.hidden 与 visibilitychange 事件,比监听窗口的 blur / focus 可靠——切换应用、锁屏、最小化都会正确改变可见性。被后台影响的只有「画面几秒没动」这件事,与记录下来的时间无关。

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

示例条件与页面打开时的默认值完全一致:目标单圈 45000 毫秒、显示 3 位小数,演示数据是 18 圈 400 米间歇跑。

显示精度与时间值换算对照表

下表左边是内部保存的毫秒原值,中间两列是同一数值在 3 位小数与 0 位小数下的显示,右边是它相对默认目标 45000 毫秒的差。末位一律截断,不四舍五入。

毫秒原值3 位小数显示0 位小数显示相对 45000 的差
4421000:00:44.21000:00:44-790
4465000:00:44.65000:00:44-350
4489000:00:44.89000:00:44-110
4500000:00:45.00000:00:450
4535000:00:45.35000:00:45+350
4572000:00:45.72000:00:45+720
4599000:00:45.99000:00:45+990
5999900:00:59.99900:00:59+14999
6000000:01:00.00000:01:00+15000
35958000:05:59.58000:05:59+314580
35999999999:59:59.99999:59:59+359954999

看第 8、9 两行就明白截断和进位的差别:59999 毫秒显示成 00:00:59 而不是 00:01:00,60000 毫秒才跳到 00:01:00。秒表沿用「没走满就不算」的惯例——向上取整会让每一圈看起来都提前一点点,累加到 18 圈就偏出好几毫秒。最后一行是显示上限:359999999 毫秒正好是 99:59:59.999,再多一位就溢出格子的位数了。

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

常见问题

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

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

「本圈用时」和「累计用时」是什么关系?

累计用时是从按下「开始」算起的总时长,「本圈用时」是它减去上一次计次时的累计快照。所以第 1 圈两个数相同(快照序列里没有前一项,减 0),第 2 圈的 00:00:44.210 等于累计 00:01:29.100 减第 1 圈的 00:00:44.890。

显示到毫秒,是不是真的精确到毫秒?

不完全是。格式给了 3 位小数,但浏览器把 performance.now() 的分辨率限制在 100 微秒(Chrome 默认)甚至 1 毫秒(Firefox 默认隐私降精度),末位数字的可信度取决于浏览器而不是格式。真正需要极细读数时只能靠机器触发,人工点击的误差在百毫秒量级。

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

累计值不会少走。页面隐藏时 requestAnimationFrame 完全停止回调,画面不刷新,但回来后的第一帧重新算 performance.now() - 基准,所以离开多久都不影响结果。会有变化的只是你在后台那段时间没看到画面跳动。

分段求和一定等于总用时吗?

等于。每一圈的分段都是相邻两个累计快照之差,求和时中间项两两抵消,只剩最后一个快照减 0。演示数据里 809910 毫秒既是分段之和,也是最后一圈的累计值,可以直接拿这一列自己验算。

为什么第 1 圈的「与上圈差」是一道短横?

因为还没有上一圈可减。第 2 圈开始才显示「本圈用时减上圈用时」的带符号毫秒数,正数表示比上一圈慢,负数表示比上一圈快。想看的其实是趋势,连续为正就说明在掉速。

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

因为光标停在了输入框里。空格、L、R 在不聚焦输入控件时才生效,否则你填目标单圈时按空格会被当成快捷键;想用快捷键先点一下页面空白处,或按 Tab 把焦点移出输入框。输入框里按 Enter 是套用数值,不是切开关。

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

不会上传,全部换算都在浏览器里完成,没有任何网络请求。但本页也不做本地存储:刷新后计时从零开始、计次表回到 18 行演示数据。要保留记录请先「复制计次表」或「导出 CSV」。

相关工具

需要从设定时长往回走、按固定周期自动换挡、或者由浏览器到点响铃的场景,本页都不承担。