Pointer and touch gestures with ease
For the most up-to-date documentation, see the Tocada GitHub repository.
npm install tocadaimport { usePointerEvents } from "tocada";
// Pass a query selector or HTMLElement (Pointer Events by default: mouse, pen, touch)
const swipeArea = usePointerEvents("#my-element");
// Listen for events
swipeArea.element.addEventListener("swipe", (e) => {
console.log("Swiped!", e.detail);
});
// Clean up when done
swipeArea.destroy();For TouchEvent-only input (legacy mobile pipelines), use useTouchEvents instead—it forces pointerEvents: false.
new Tocada(selectorOrElement) is the same as passing { pointerEvents: true } (Pointer Events). Use { pointerEvents: false } or useTouchEvents(...) only when you need the legacy touch stack.
| Event | Description |
|---|---|
tap |
Quick touch < 200ms |
doubletap |
Two taps within 300ms |
press |
Touch held 200-500ms |
hold |
Touch held > 500ms |
swipe |
Fires before directional swipe events (same gesture also emits swipeup / swipedown / etc.) |
swipeup |
Swipe in upward direction |
swipedown |
Swipe in downward direction |
swipeleft |
Swipe in left direction |
swiperight |
Swipe in right direction |
swipeclockwise |
Circular swipe in clockwise direction |
swipecounterclockwise |
Circular swipe in counter-clockwise direction |
| Event | Description |
|---|---|
gesture |
Fires before any multi-contact gesture (touchCount is active pointer / touch count) |
pinch |
Two fingers moving closer together |
spread |
Two fingers moving apart |
rotate |
Two-finger rotation (fires before directional) |
rotateclockwise |
Clockwise two-finger rotation |
rotatecounterclockwise |
Counter-clockwise two-finger rotation |
import { usePointerEvents } from "tocada";
const swipeArea = usePointerEvents("#my-element", {
// Prefix all event names (e.g., "myapp-swipe", "myapp-tap")
eventPrefix: "myapp-",
// Enable high-precision element tracking (fills gaps between move events)
// Adds computational overhead - use when you need complete element coverage
useHighPrecision: true,
// Inline touch-action on the target element (default: "none").
// Suppresses native pan/pinch on that surface so gestures stay accurate; restored on destroy().
// Use false to leave touch-action unchanged, or a CSS value (e.g. "manipulation", "pan-y").
touchAction: "none",
// Customize detection thresholds
thresholds: {
swipeThreshold: 50, // Min distance for swipe (px)
tapMaxTime: 200, // Max duration for tap (ms)
doubleTapGap: 300, // Max gap between taps for doubletap (ms)
pressMinTime: 200, // Min duration for press (ms)
holdMinTime: 500, // Min duration for hold (ms)
circularSwipeMinArc: 90, // Min arc for circular swipe (degrees)
// NOT YET IMPLEMENTED palmMinTouches: 3, // Min touch points for palm swipe
// NOT YET IMPLEMENTED palmLineTolerance: 50, // Tolerance for palm line detection (px)
rotateMinAngle: 15, // Min angle for rotation (degrees)
pinchSpreadMinDistance: 20, // Min finger distance change for pinch/spread (px)
}
});
// With prefix, listen like this:
swipeArea.element.addEventListener("myapp-swipe", (e) => {
console.log("Swiped!", e.detail);
});By default, Tocada sets touch-action: none on the element’s inline style so the browser does not steal the gesture for native pan/zoom while you track pointers or touches. The previous inline value is restored when you call destroy() (or the property is removed if there was none).
| Option | Behavior |
|---|---|
| (omitted) | Sets touch-action: none |
touchAction: false |
Does not change touch-action (use when the region must scroll or zoom normally) |
touchAction: "pan-y" (etc.) |
Sets that keyword; still restored on destroy() |
This applies to new Tocada(...), usePointerEvents(...), and useTouchEvents(...).
Each event type provides a detail object with relevant data.
{
velocity, // Overall speed (px/ms)
velocityX, // X-axis speed
velocityY, // Y-axis speed
distance, // Total distance traveled
distanceX, // X-axis distance
distanceY, // Y-axis distance
avgPressure, // Average touch pressure
startPressure, // Starting pressure
endPressure, // Ending pressure
startTime, // Start timestamp
endTime, // End timestamp
startingElement, // First element touched
endingElement, // Last element touched
touchedElements, // All elements touched during swipe
startingCoords, // { x, y } start position
endingCoords, // { x, y } end position
// High precision fields (only when useHighPrecision: true)
touchedPathElements?, // Elements found by sampling touchPath coordinates
interpolatedTouchedElements?, // Elements found via interpolation between touchmove events
derivedTouchedElements?, // Combined and chronologically ordered array (recommended)
}{
duration, // How long the touch lasted (ms)
pressure, // Touch pressure
element, // Element that was tapped
coords, // { x, y } tap position
startTime, // Start timestamp
endTime, // End timestamp
}Detection uses sampled move points plus the lift position (pointer up / touch end), and denoises dense or jittery input so direction stays stable. Tune sensitivity with thresholds.circularSwipeMinArc (default 90 degrees).
{
direction, // "clockwise" or "counterclockwise"
arc, // Total arc traversed (degrees)
touchPath, // Array of { x, y, time } points
touchedElements, // All elements touched during circular swipe
// High precision fields (only when useHighPrecision: true)
touchedPathElements?, // Elements found by sampling touchPath coordinates
interpolatedTouchedElements?, // Elements found via interpolation between touchmove events
derivedTouchedElements?, // Combined and chronologically ordered array (recommended)
}{
angle, // Total rotation (degrees)
direction, // "clockwise" or "counterclockwise"
startAngle, // Starting angle
endAngle, // Ending angle
centerPoint, // { x, y } center of rotation
}{
gesture, // "pinch" or "spread"
startDistance, // Initial distance between fingers
endDistance, // Final distance between fingers
distanceChange, // Change in distance
scale, // endDistance / startDistance
centerPoint, // { x, y } center point
}{
touchCount, // Active contacts (pointers or touches, depending on pipeline)
}When useHighPrecision: true is enabled, Tocada provides additional element tracking arrays to fill gaps between discrete move samples (pointermove or touchmove, depending on pipeline) during rapid swipes. This is useful for:
- Visual feedback: Highlighting all elements in a swipe path
- Game interactions: Detecting all tiles/elements touched during a gesture
- Complete coverage: Ensuring no elements are missed during fast swipes
const swipeArea = usePointerEvents("#my-element", {
useHighPrecision: true
});
swipeArea.element.addEventListener("swipe", (e) => {
// Use derivedTouchedElements for complete, ordered coverage
const allElements = e.detail.derivedTouchedElements || e.detail.touchedElements;
allElements.forEach(el => el.classList.add("highlighted"));
});When useHighPrecision: true, three additional arrays are provided:
-
touchedPathElements: Elements found by sampling coordinates from thetouchPatharray. This fills gaps by checking elements at points along the recorded touch path. -
interpolatedTouchedElements: Elements found via interpolation between consecutive move events. Samples points every 5–10px along the interpolation path to catch elements that might have been missed. -
derivedTouchedElements: Recommended to use. A combined array that mergestouchedElements,interpolatedTouchedElements, andtouchedPathElements, then orders them chronologically by their position in the touch path and deduplicates them.
High precision tracking adds computational overhead as it:
- Samples multiple points along the path
- Calls
document.elementFromPoint()for each sampled point - Performs interpolation calculations during move events
Only enable this feature when you need complete element coverage. For most use cases, the standard touchedElements array is sufficient.
For custom tracking needs or edge cases, you can use native browser APIs:
document.elementFromPoint(x, y): Get the topmost element at specific coordinatesdocument.elementsFromPoint(x, y): Get all elements at coordinates (in stack order)PointerEvent: When using the default pointer pipeline,pressure,pointerId, and coalesced move events are available from the browser.TouchEvent.touches(touch pipeline only): Access raw touch data during move eventsTouchEvent.changedTouches(touch pipeline only): Touches that changed in the current event
These native APIs can be useful for:
- Custom interpolation logic
- Handling edge cases not covered by Tocada
- Building specialized gesture tracking features
- Debugging pointer or touch behavior
Tocada is written in TypeScript and exports all types:
import Tocada, {
usePointerEvents,
useTouchEvents,
ITocadaOptions, // includes touchAction?: false | string
ISwipeEventDetails,
ITapEventDetails,
IRotateEventDetails,
IPinchSpreadEventDetails,
// IPalmSwipeEventDetails, NOT YET IMPLEMENTED
ICircularSwipeEventDetails,
DEFAULT_THRESHOLDS,
} from "tocada";I write a lot of open source software (some more useful than others). You can help me out by tossing me a few bucks to buy coffee.
