SwipeController

Reports a left, right, up or down swipe on release.

Reach for it for a carousel or pager.

Import

import { SwipeController, createSwipeController } from '@ecopages/radiant-ui/controllers';

Try it

Press on the box and either move at least 48 px sideways or flick it at least 16 px faster than 0.5 px per ms, then release. A drag that is not at least twice as far sideways as it is up or down is ignored. The callback reports the direction.

Swipe me

No swipe yet

<div data-example="swipe">
	<div data-box>Swipe me</div>
	<p data-status>No swipe yet</p>
</div>
import { SwipeController } from '@ecopages/radiant-ui/controllers';
 
export function mount(root: HTMLElement): () => void {
	const box = root.querySelector<HTMLElement>('[data-box]')!;
	const status = root.querySelector('[data-status]')!;
 
	const swipe = new SwipeController({
		getTarget: () => box,
		minVelocity: 0.5,
		dominance: 2,
		onSwipe: (direction) => (status.textContent = `Swiped ${direction}`),
	});
	swipe.attach();
	return () => swipe.destroy();
}

mount runs once for each example element on the page, and the function it returns undoes it; see how the examples run.

Config

OptionDescription
getTargetResolved on every attach(). null listens to nothing.
axisWhich travel counts: x (default), y or both.
thresholdMinimum travel in px along the swipe axis. Default 48.
minVelocitySpeed in px per ms along the axis that lets a shorter travel count, a flick. The travel must still reach 16 px (MIN_FLICK_TRAVEL_PX). Default: none, only threshold decides.
dominanceHow many times larger the travel along the axis must be than across it. Default 1, which is simply larger.
getHandleLimits gestures to this element's subtree, resolved on every press; use it with the host as getTarget when the surface is a child that can be replaced.
excludeA gesture that starts on a target this returns true for is ignored, such as a button inside the surface.
onSwipeCalled on the pointer-up that ends a swipe, with the direction and the event.

Lifecycle

attach() listens for a press on the target; release and cancel are then listened for on document, so a release outside the element still ends the gesture. It reports once, on release, and does not follow the pointer; a surface that moves with the pointer needs PointerDragController. A cancel or destroy() drops a gesture in progress, and destroy() removes the listeners.

With a host, createSwipeController(host, config) does the same on connect and disconnect; see bindController.

Protected hooks

Extend the class and override these to change how it decides. A component never extends a controller; a subclass is registered with bindController.

HookDescription
resolveDirection(dx, dy, elapsedMs)Decides which swipe, if any, a gesture was. Runs on the pointer-up with the travel from the press point and the milliseconds since the press, taken from the timeStamp of the two pointer events. Return null for no swipe. The default is resolveSwipeDirection with the configured axis, threshold, minVelocity and dominance.