useDrag
useDrag(target: HTMLElement | SVGElement, options?: DragOptions): Service<DragProps>A drag gesture, with an optional inertia phase.
Props
interface DragProps {
readonly mode: DragMode;
readonly x: number;
readonly y: number;
readonly deltaX: number;
readonly deltaY: number;
readonly originX: number;
readonly originY: number;
readonly distanceX: number;
readonly distanceY: number;
readonly finalX: number;
readonly finalY: number;
}isGrabbing, hasInertia and target are removed, and props.MODES with them.
The modes
import { DRAG_MODES } from '@studiometa/js-toolkit';
DRAG_MODES.IDLE; // 'idle'
DRAG_MODES.START; // 'start'
DRAG_MODES.DRAG; // 'drag'
DRAG_MODES.DROP; // 'drop'
DRAG_MODES.INERTIA; // 'inertia'
DRAG_MODES.STOP; // 'stop'idle is new in v4. DragMode is derived from the frozen object — that is the pattern for every closed set of strings in the framework.
Options
interface DragOptions {
axis?: 'x' | 'y' | 'both';
dampFactor?: number;
dragThreshold?: number;
inertia?: boolean;
}The v3 spelling dragTreshold is fixed.
Usage
import { Base, DRAG_MODES, useDrag } from '@studiometa/js-toolkit';
class Slide extends Base {
static config = { name: 'Slide' };
mounted() {
return useDrag(this.$el, { axis: 'x' }).subscribe(({ mode, x, finalX }) => {
if (mode === DRAG_MODES.DROP) {
this.settle(finalX);
return;
}
this.$el.style.transform = `translateX(${x}px)`;
});
}
settle(x) {}
}A gesture the browser can steal is not a gesture
useDrag owns both axes by default and writes touch-action: none.
axis | writes | and |
|---|---|---|
'both' | none | — |
'x' | pan-y | every Y movement prop is zero |
'y' | pan-x | every X movement prop is zero |
It writes only when the computed value is auto, and it restores the previous inline value when the last service of those options leaves. Services on one target share that ownership.
The click that ends a drag is suppressed from a flag that movement on the owned axis arms and the next pointerdown disarms — and only for a trusted click with a non-zero detail.
Inertia
With { inertia: false } a drag publishes the exact projected destination at drop, then goes through stop to idle with no tick subscription at all.
With inertia, the coast subscribes to scheduler.tick(). Decay is expressed in time, not in frames:
INERTIA_FRAME(16.67 ms) is the reference of every factor;inertiaStep()integrates the decay across the step, so any sequence of frames sums tovelocity · τexactly;- the settle position is
value + velocity · τwithτ = INERTIA_FRAME / ln(1 / damp).
The velocity is sampled as a distance over the interval between events, smoothed, with the interval clamped at both ends. At the drop, the velocity is decayed by the idle time through the same law — so a pointer that stopped moving before release does not fling.
Keying
useDrag() keys its axis, inertia, damping and threshold, so two callers asking for the same gesture on the same element share one service. See perTarget().
{ immediate: true }
Does nothing outside a gesture: there is no current value between gestures.
Mixin
class Slide extends withDrag(Base, { axis: 'x', inertia: false }) {
dragged({ mode, x }) {}
}