tap
Tap gesture recognition with multi-tap support. Detects single, double, triple taps and beyond with configurable timing and distance thresholds.
Basic Usage
import { tap } from "cereb";
tap(element).on((signal) => { const { tapCount, cursor } = signal.value; const [x, y] = cursor;
if (tapCount === 1) { console.log("Single tap"); } else if (tapCount === 2) { console.log("Double tap - zoom in!"); }});Signature
function tap(target: EventTarget, options?: TapOptions): Stream<TapSignal>Options
| Option | Type | Default | Description |
|---|---|---|---|
movementThreshold | number | 10 | Max movement (px) allowed during tap |
durationThreshold | number | 500 | Max duration (ms) for a valid tap |
chainMovementThreshold | number | movementThreshold | Max distance between consecutive taps |
chainIntervalThreshold | number | durationThreshold / 2 | Max interval (ms) between consecutive taps |
Multi-Tap Configuration
// Fast double-tap detectiontap(element, { durationThreshold: 300, chainIntervalThreshold: 250})
// Strict tap positioningtap(element, { movementThreshold: 5, chainMovementThreshold: 20})Signal Value
The signal.value contains:
| Property | Type | Description |
|---|---|---|
phase | "start" | "end" | "cancel" | Current gesture phase |
cursor | [number, number] | Tap position (client coordinates) |
pageCursor | [number, number] | Tap position (page coordinates) |
tapCount | number | Consecutive tap count (1, 2, 3, …) |
duration | number | How long pointer was pressed (ms) |
pointerType | "mouse" | "touch" | "pen" | "unknown" | Input device type |
Phase Lifecycle
pointer down → "start" → pointer up (within thresholds) → "end" → moved too far / held too long → "cancel"- start: Pointer pressed down
- end: Valid tap completed (tapCount incremented if chained)
- cancel: Tap invalidated (moved too far or held too long)
Multi-Tap Chaining
Taps are chained when:
- Time between taps is less than
chainIntervalThreshold - Distance between tap positions is less than
chainMovementThreshold
tap → 200ms → tap → 200ms → tap = tapCount: 3 (triple tap)tap → 500ms → tap = tapCount: 1 (chain reset)With Visual Feedback
Use tapRecognizer operator for full lifecycle handling:
import { singlePointer, tapRecognizer } from "cereb";
singlePointer(element) .pipe(tapRecognizer()) .on((signal) => { const { phase } = signal.value;
if (phase === "start") { element.classList.add("pressed"); } else { element.classList.remove("pressed"); } });Filter to End Phase Only
Use tapEndOnly to only receive successful taps:
import { singlePointer, tapEndOnly } from "cereb";
singlePointer(element) .pipe(tapEndOnly()) .on((signal) => { console.log(`Tap ${signal.value.tapCount}!`); });