Skip to content

andyed/clicksense

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ClickSense

Click confidence measurement from motor behavior. 2KB, zero dependencies, vendor-agnostic.

The idea

A mouse click is two events: mousedown (commitment) and mouseup (release). The latency between them — typically 50-140ms — reflects the motor system's confidence at the moment of action. ClickSense captures this hold duration alongside the mouse's approach trajectory leading up to the click.

Hold duration alone is a small signal (~7ms task effect across studies). The real leverage is in combining it with pre-click motor behavior: how the cursor approached the target, whether it decelerated smoothly or corrected course, and how long it paused before committing. Pre-click dwell discriminates correct from incorrect selections by 91ms; hold duration doesn't. But they measure adjacent phases of the same decision — evaluation then commitment — and the combination may access something neither captures alone.

What makes hold duration interesting is what it's independent from. Self-reported cognitive load (NASA-TLX) doesn't correlate with it. Neither does task correctness. It's not a noisy proxy for things we already measure. The strongest effects show up around identity-relevant content and domain expertise: political experts differentiate facts from opinions in their click latency; non-experts don't. Non-voters hold 19ms longer on prospective voting questions than retrospective ones.

Reference: Edmonds (2016) "Learning from Complex Online Behavior", Bloomreach "Big Brains" talk. Data: Edmonds (CrowdFlower, 2015, n=291), Azzopardi & Edmonds (Prolific, 2022, n=227).

Install

npm install clicksense

Or load via script tag:

<script src="dist/clicksense.js"></script>

Quick start

import { ClickSense } from 'clicksense';

const cs = new ClickSense({
  enableApproachDynamics: true,
  onCapture: (event) => {
    console.log(`${event.duration_ms}ms on ${event.target.tag}`, event);
  },
});

Each captured click produces:

{
  duration_ms: 87,              // mousedown→mouseup latency
  timestamp: 1709312400000,
  x: 512, y: 340,
  drag_distance: 1,
  target: {
    tag: 'button',
    id: 'submit-btn',
    label: 'checkout',          // from data-clicksense="checkout"
    text: 'Complete Purchase',
  },
  approach: {                   // present when enableApproachDynamics: true
    approach_velocity_mean: 0.412,
    approach_velocity_final: 0.089,
    approach_deceleration: -0.001234,
    approach_corrections: 3,
    approach_distance: 287,
    approach_pause_ms: 42,
  }
}

Approach dynamics

The pre-click mouse trajectory is where the stronger signal lives. When enableApproachDynamics is enabled, ClickSense maintains a ring buffer of velocity samples from mousemove events (using getCoalescedEvents() for sub-frame resolution on high-polling-rate mice) and harvests summary statistics at the moment of mousedown:

Field Description
approach_velocity_mean Mean cursor speed over last 500ms (px/ms)
approach_velocity_final Speed at the most recent sample before click
approach_deceleration Linear regression slope of velocity over last 300ms. Negative = smooth Fitts's law approach; positive = overshooting
approach_corrections Velocity direction reversals — jittery approach vs. smooth ballistic arc
approach_distance Total cursor distance traveled (px)
approach_pause_ms Time since last significant movement. 0 = clicked while moving; 100+ = paused to aim

A confident click looks like: smooth deceleration, low corrections, short pause, ballistic hold. An uncertain click: course corrections, longer pause, extended hold. The combination captures the full motor signature of a decision.

Options

new ClickSense({
  enableApproachDynamics: true, // recommended — pre-click velocity profiling
  minDuration: 10,              // ms — below = programmatic click
  maxDuration: 3000,            // ms — above = press-and-hold
  maxDragDistance: 10,           // px — above = drag, not click
  captureText: true,            // include truncated innerText
  textMaxLength: 80,
  buttons: [0],                 // 0=left, 1=middle, 2=right
  scope: null,                  // CSS selector to limit tracking
  onCapture: (event) => {},     // required
});

Adapters

PostHog

import { ClickSense } from 'clicksense';
import { createPostHogAdapter } from 'clicksense/adapters/posthog';

new ClickSense({
  onCapture: createPostHogAdapter(),            // uses window.posthog
  // or: createPostHogAdapter(posthogInstance)   // explicit instance
});
// Sends 'click_confidence' events with flattened properties

Buffered callback

For sendBeacon, Adobe Analytics, custom endpoints, or localStorage:

import { ClickSense } from 'clicksense';
import { createBufferedAdapter } from 'clicksense/adapters/callback';

new ClickSense({
  onCapture: createBufferedAdapter({
    flushInterval: 10000,   // flush every 10s (default: 30s)
    maxBuffer: 200,         // flush at 200 events
    onFlush: (events) => {
      navigator.sendBeacon('/analytics', JSON.stringify(events));
    },
  }),
});

Flushes automatically on visibilitychange (page hide/tab switch).

Target extraction

ClickSense walks the DOM from the click target to find the nearest meaningful element (a, button, [role="button"], input, select, label, or [data-clicksense]). Use the data-clicksense attribute for explicit labeling:

<div data-clicksense="hero-cta" class="promo-card">
  <h2>Spring Sale</h2>
  <button>Shop Now</button>
</div>

Design decisions

  • Capture phase listeners — events are seen before stopPropagation
  • performance.now() — sub-millisecond precision, not Date.now()
  • Drag filter — displacement > 10px between down/up = drag, discarded
  • Touch supporttouchstart/touchend for mobile
  • No runtime dependencies — core is pure JS, adapters are optional

Cleanup

cs.destroy(); // removes all listeners

Build

npm run build   # → dist/clicksense.js (IIFE), .esm.js, .cjs.js
npm run dev     # watch mode

Research

See docs/clicksense-paper.md for the full empirical framing: study designs, effect sizes, confound analysis, and where click duration does and doesn't work.

License

MIT

About

Mousedown→mouseup latency as cognitive load signal. Vendor-agnostic click confidence measurement.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors