Gestures
Touch gesture library for swipe detection, swipe-to-dismiss, pull-to-refresh, long-press, and haptic feedback. Pointer-event based, works on touch, mouse, and pen.
Overview
The gesture module (src/lib/vb-gestures.js) provides five named exports:
| Export | Purpose |
|---|---|
addSwipeListener |
Detect swipe direction on an element |
makeSwipeable |
Drag-to-dismiss with visual feedback |
addPullToRefresh |
Pull-down spinner with async callback |
addLongPress |
Timer-based long-press detection |
haptic |
Vibration patterns for feedback (Android) |
Every function returns a cleanup function for teardown. All use PointerEvent (not TouchEvent) and check e.isPrimary to ignore multi-touch.
Installation
The module lazy-loads automatically when any data-gesture attribute exists on the page. Just add the attribute and gestures work:
For programmatic use (like pull-to-refresh which requires a callback), import the module directly:
Swipe Detection
Detects directional swipes by comparing pointerdown and pointerup positions. Dispatches swipe-left, swipe-right, swipe-up, or swipe-down events on the element.
Options
| Option | Default | Description |
|---|---|---|
threshold |
50px | Minimum distance to qualify as a swipe |
restraint |
100px | Maximum perpendicular distance |
timeout |
300ms | Maximum time between down and up |
The 50px default threshold avoids conflict with iOS Safari’s edge-swipe back gesture, which activates from the left ~20px.
Swipe-to-Dismiss
Full drag tracking with pointer capture. The element translates and fades as you drag, then either snaps back or slides off screen.
State is managed via data attributes: data-swiping during drag, data-dismissed after dismiss. The snap-back uses a spring easing for a natural feel.
Pull-to-Refresh
Creates a spinner indicator inside a scroll container. The spinner appears when pulled past threshold; your async callback runs while the spinner shows.
Requirements
- Container must have
overflow-y: autoorscroll - Add
overscroll-behavior-y: containon the container to disable Chrome’s native pull-to-refresh - Add
position: relativeon the container for indicator positioning
All listeners use { passive: true } for best scroll performance.
Long Press
Timer-based detection: starts a setTimeout on pointerdown, cancels on pointermove, pointerup, or pointercancel.
Use data-gesture="long-press" for declarative usage — the element dispatches a long-press custom event that you can listen for.
Haptic Feedback
Four vibration patterns for tactile feedback. Uses the Vibration API, which is Android-only — calls no-op silently on iOS and desktop.
| Method | Pattern | Use case |
|---|---|---|
haptic.tap() |
8ms | Selection, toggle, checkbox |
haptic.confirm() |
8-40-8ms | Form submit, save, success |
haptic.error() |
30-60-30ms | Validation failure, error |
haptic.dismiss() |
15ms | Delete, dismiss, destructive |
CSS Utilities
The gesture module includes a companion CSS file that styles gesture states. It’s imported into the utils layer automatically.
Reduced-motion users see no spinner animation, and will-change is removed to avoid unnecessary compositing.
Carousel Integration
View Transition carousels (data-transition) automatically load the gesture module and add swipe navigation. Normal scroll-snap carousels handle touch natively and don’t use gestures.
The gesture module is dynamically imported only when a VT carousel is present, so it adds no overhead to pages without one.
Browser Support
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| PointerEvent | 55+ | 59+ | 13+ | 12+ |
setPointerCapture |
55+ | 59+ | 13+ | 12+ |
touch-action |
36+ | 52+ | 10+ | 12+ |
| Vibration API | 32+ | 16+ | No | 79+ |
All gesture features work in all modern browsers. Haptic feedback is Android-only; the haptic object no-ops silently on platforms without Vibration API support.