DEV Community

Ling Chen
Ling Chen

Posted on

Building a Browser-Based Click Interval Logger: Lessons from Diagnosing Mouse Chatter

A friend's mouse kept opening folders on a single click. Before buying a new one, I wanted evidence. That led me down a small rabbit hole: how precisely can a web page measure mouse clicks, and what can it actually tell you about hardware? Here is what I learned.

The goal

Detect "chatter", where a worn mouse switch sends two press signals for one physical press. Humans cannot press the same button twice within a few milliseconds, so very short intervals between presses are a red flag.

Step 1: Capture events

The browser gives us pointerdown and mousedown events. I used pointerdown because it unifies mouse, pen and touch, and I filtered for mouse input:

const log = [];
let last = {};

window.addEventListener("pointerdown", (e) => {
  if (e.pointerType !== "mouse") return;
  const t = e.timeStamp;            // DOMHighResTimeStamp, in ms
  const prev = last[e.button];
  const interval = prev === undefined ? null : t - prev;
  log.push({ button: e.button, t, interval });
  last[e.button] = t;
});
Enter fullscreen mode Exit fullscreen mode

e.button is 0 for left, 1 for middle, 2 for right, 3 for back and 4 for forward.

Step 2: Prevent the page from interfering

Right-clicks open a context menu, and middle-clicks may trigger auto-scroll. To test those buttons, suppress the defaults inside the test area:

area.addEventListener("contextmenu", (e) => e.preventDefault());
area.addEventListener("auxclick", (e) => e.preventDefault());
area.addEventListener("mousedown", (e) => { if (e.button === 1) e.preventDefault(); });
Enter fullscreen mode Exit fullscreen mode

Step 3: Decide what counts as suspicious

I asked users to press one button 25 times, slowly. Then:

const THRESHOLD_MS = 40;
const flagged = log.filter((r) => r.interval !== null && r.interval < THRESHOLD_MS);
Enter fullscreen mode Exit fullscreen mode

Why 40 ms? Deliberate repeated presses are usually well above 100 ms. Even fast "butterfly clicking" rarely goes below about 40–50 ms between the same button's presses. Anything in single-digit milliseconds is almost certainly not a human.

Thresholds are judgement calls, so it is best to show users the raw intervals as well as the flags.

Step 4: Know the limits of browser timing

This was the most important lesson.

  • Timer precision is reduced. To mitigate Spectre-style attacks, browsers coarsen performance.now() and event.timeStamp, sometimes to 0.1 ms, sometimes to 1 ms or more, especially without cross-origin isolation.
  • Events are already processed. The OS and the mouse firmware debounce signals before the browser sees them. You measure what reaches the page, not the electrical switch.
  • Polling rate matters. A 125 Hz mouse reports every 8 ms, while a 1000 Hz mouse reports every 1 ms. Two events less than one polling interval apart cannot be distinguished.
  • Main-thread jank. Heavy work on the page can delay handlers. Keep the test page light.

So a browser test is a diagnostic aid, not lab equipment. A clear pattern of flagged ultra-short intervals across several runs is strong evidence. A single flag is not.

Step 5: Export the evidence

Warranty claims go faster with data, so I added a CSV export:

function toCSV(rows) {
  const head = "index,button,timestamp_ms,interval_ms";
  const body = rows.map((r, i) =>
    [i + 1, r.button, r.t.toFixed(2), r.interval?.toFixed(2) ?? ""].join(",")
  );
  return [head, ...body].join("\n");
}
const blob = new Blob([toCSV(log)], { type: "text/csv" });
Enter fullscreen mode Exit fullscreen mode

Step 6: UX details that mattered

  • Show a big counter (for example "17 / 25") so users know how far they are.
  • Tell users explicitly to press slowly. Fast clicking produces false positives.
  • Run separate tests per button.
  • Offer a reset, and keep the history of multiple runs.

The result

My friend's left button produced intervals of 3–9 ms on four of 25 presses, consistently across three runs. The right button was clean. That was enough evidence to file a warranty claim, which was approved.

Don't want to build it yourself?

If you just need to check a mouse, Double Click Test implements essentially this approach. It runs 25 presses, logs the intervals, flags short repeats and exports a CSV, with extra pages for other buttons, the scroll wheel and jitter. Building my own was a fun exercise in browser event timing, and the same caveats apply to any tool like this.

Takeaways

  1. event.timeStamp is good enough to spot chatter, but not for millisecond-precise lab measurement.
  2. Browsers see post-firmware, post-OS events.
  3. Polling rate and timer coarsening set the floor on what you can detect.
  4. Clear UX, such as slow presses and per-button tests, matters as much as code.
  5. Exportable logs turn a hunch into evidence.

Top comments (0)