I built a reaction time click test. You click when the background turns green, it tells you how fast you were. Simple enough.
The first version just used setTimeout and Date.now(). The numbers were garbage — 180ms one click, 240ms the next. I sat there clicking for 20 minutes wondering if my reflexes were broken.
Turns out the problem was hiding in plain sight in the MDN docs.
The 4ms Clamp Nobody Tells You About
setTimeout(fn, 0) doesn't actually fire in 0ms. According to the HTML spec, browsers clamp it to 4ms minimum after 5 nested calls. Firefox, Chrome, and Safari all do this.
And if the tab is in the background? Throttled to 15ms or more by the browser's scheduler. Every single measurement had this random 4–15ms of extra latency baked in.
requestAnimationFrame + performance.now() Fixes It
// Bad: setTimeout adds noise
setTimeout(() => {
goTime = Date.now();
showGreen();
}, randomDelay);
// Good: rAF + performance.now()
const scheduleGo = () => {
const delay = 1000 + Math.random() * 4000;
setTimeout(() => {
requestAnimationFrame(() => {
goTime = performance.now();
showGreen();
});
}, delay);
};
rAF schedules the green flash on the next frame boundary — no racing between your timer and the compositor. Result: standard deviation dropped from ~40ms to ~8ms.
Mobile Is a Different Beast
Same code was reading 60–80ms slower on my phone. Touch events have a physical delay — capacitive touchscreens take ~50ms to register, plus software debounce. Nothing you can do in JS, it's hardware.
pointerdown instead of click helps. touch-action: manipulation removes the 300ms double-tap delay. But you'll never get desktop-speed responses on a phone screen.
| Timing method | Precision | Issues |
|---|---|---|
Date.now() |
1ms | Clock skew |
performance.now() |
~5μs | Monotonic |
setTimeout(fn, 0) |
4ms+ | Clamped, throttled |
requestAnimationFrame |
~16ms (60Hz) | Syncs with refresh |
Key insight: use setTimeout for scheduling, performance.now() for measuring.
Live version: checkreaction.com
Source: github.com/dayu2333-jinyul/reaction-timer-demo
Top comments (0)