Gesture options

β€ŒπŸ‘‡ @use-gesture offers different options to configure the gestures.

Some options are generic to the way πŸ–– @use-gesture behaves while some other options can be configured per gesture.

Structure of the config object

Depending on whether you use gesture-specific hooks or if you use the useGesture hook or Gesture class, you'll need to structure the config option object differently.

// when you use a gesture-specific hook
useDrag(state => doSomethingWith(state), { ...genericOptions, ...dragOptions })
// when you use the useGesture hook
useGesture({
onDrag: state => doSomethingWith(state),
onPinch: state => doSomethingWith(state),
// ...
{
// global options such as `target`
...genericOptions,
// gesture specific options
drag: dragOptions,
wheel: wheelOptions,
pinch: pinchOptions,
scroll: scrollOptions,
wheel: wheelOptions,
hover: hoverOptions,
}
})
// when you use a gesture-specific class
new DragGesture(
element,
state => doSomethingWith(state),
{ ...genericOptions, ...dragOptions }
)
// when you use the Gesture class
new Gesture(element, {
onDrag: state => doSomethingWith(state),
onPinch: state => doSomethingWith(state),
// ...
{
// global options such as `target`
...genericOptions,
// gesture specific options
drag: dragOptions,
wheel: wheelOptions,
pinch: pinchOptions,
scroll: scrollOptions,
wheel: wheelOptions,
hover: hoverOptions,
}
})

Generic options

Generic options deal with how πŸ‘ˆ @use-gesture will set event listeners.

OptionDescription
targetLets you specify a dom node or React ref you want to attach the gesture to.
eventOptionsLets you customize if you want events to be passive or captured.
windowLets you specify which window element the gesture should bind events to (only relevant for the drag gesture).
enabledWhen set to false none of your handlers will be fired.

Gesture options

Here are all options that can be applied to gestures.

All options are not available to all gestures. In the table below xy designates coordinates-based gestures: drag, move, wheel and scroll.

OptionsGesturesDescription
enabledallWhether the gesture is enabled.
fromallThe initial position offset should start from.
thresholdallThe handler will fire only when the gesture displacement is greater than the threshold.
preventDefaultallWill preventDefault all events triggered by the handler.
triggerAllEventsallForces the handler to fire even for non intentional displacement (ignores the threshold). In that case, the intentional attribute from state will remain false until the threshold is reached.
axisallYour handler will only trigger if a movement is detected on the specified axis.
boundsxyLimits the gesture offset to the specified bounds.
scaleBoundspinchLimits the scale offset to the specified bounds.
angleBoundspinchLimits the angle offset to the specified bounds.
rubberbandallThe elasticity coefficient of the gesture when going out of bounds. When set to true, the elasticity coefficient will be defaulted to 0.15
transformallA function that you can use to transform pointer values. Useful to map your screen coordinates to custom space coordinates such as a canvas.
filterTapsdragIf true, the component won't trigger your drag logic if the user just clicked on the component.
preventScrolldragIf set, the drag will be triggered after the duration of the delay (in ms) and will prevent window scrolling. When set to true, preventScroll is defaulted to 250ms.
preventScrollAxisdragIf set, the drag will allow scrolling in the direction of the axis/axes unless the preventScroll duration has elapsed. Defaults to only 'y'.
pointer.touchdrag,pinchIf true, drag and pinch will use touch events on touch-enabled devices. Read more below.
pointer.capturedragIf false, drag will not use setPointerCapture and attach pointerMove events to the window. Read more below.
pointer.lockdragIf true, the pointer will enter pointer lock mode when drag starts, and exit pointer lock when drag ends. Read more below.
delaydragIf set, the handler will be delayed for the duration of the delay (in ms) β€” or if the user starts moving. When set to true, delay is defaulted to 180ms.
swipe.distancedragThe minimum distance per axis (in pixels) the drag gesture needs to travel to trigger a swipe.
swipe.velocitydragThe minimum velocity per axis (in pixels / ms) the drag gesture needs to reach before the pointer is released.
swipe.durationdragThe maximum duration in milliseconds that a swipe is detected.
mouseOnlyhover, moveSet to false if you want your hover or move handlers to be triggered on non-mouse events. This is a useful option in case you want to perform logic on touch-enabled devices.

Options explained

target (React only)

Types
node | Ref
Default
undefined

In πŸ‘ˆ @use-gesture/vanilla, you need to attach a target to the gesture as its first argument. Therefore this options is irrelevant.

β€ŒπŸ€ž @use-gesture/react supports adding handlers to dom nodes directly (or the window or document objects). In that case, you shouldn't spread the bind() object returned by the hooks as a prop. Cleaning is handled automatically.

function ScrollExample() {
const [{ width }, api] = useSpring(() => ({ width: '0%' }))
const height = document.documentElement.scrollHeight
useScroll(({ xy: [, y] }) => api.start({ width: (y / height) * 100 + '%' }), { target: window })
return <animated.div style={{ width }} />
}

The code above binds the scroll gesture to the document window, and acts as a scroll indicator. Try scrolling the page and you'll see the blue bar progress.

You can also directly pass a ref to target. This is actually usefull when you want your events not to be passive.

const myRef = React.useRef(null)
// This will add a scroll listener the div
useScroll(({ event }) => event.preventDefault(), {
target: myRef,
eventOptions: { passive: false }
})
return <div ref={myRef} />

eventOptions

Type
{capture: boolean, passive: bolean}
Default
{capture: false, passive: true}

When eventOptions.capture is set to true, events will be captured.

eventOptions.passive sets whether events are passive.

React warning: if you want events not to be passive, you will need to attach events directly to a node using target because of the way React handles events.

window

Type
node
Default
window

Lets you specify which window element the gesture should bind events to (only relevant for the drag gesture).

enabled

Type
boolean
Default
true

Whether the gesture is enabled.

from

Types
vector | (gestureState) => vector
Default
[0,0]

Everytime a gesture starts, the offset state attribute starts with its previous value. But in some cases, you might want to start offset from an initial position that is external to your logic1.

Let's take a tangible example: say that a draggable component turns back to its initial position slowly. In the meantime, the draggable component should still be interruptible at any moment. In that case, you can use initial to set the position of the component at the moment the user drags it to the value of the spring.

Drag the blue square and before it goes back to its origin drag it again. If you've unticked the checkbox, you'll notice that the square goes back to its origin instead of moving from where you've dragged it: that's because offset is by default reset to [0,0].

The code below shows how the example works:

function InitialExample() {
const [{ x }, api] = useSpring(() => ({ x: 0 }))
const bind = useDrag(
({ down, offset: [ox] }) => api.start({ x: down ? ox : 0, immediate: down, config: { duration: 3000 } }),
{ from: () => [x.get(), 0] }
)
return <animated.div {...bind()} style={{ x }} />
}

Unless your initial position is static or depends on state, make sure you use a function rather than a static array.

threshold

Types
vector | number
Default
[0,0]

By default, your gesture handler will be triggered as soon as an event is fired. However, there are situations where you want to make sure the user action is intentional: that's where threshold comes into play.

threshold is the minimum displacement the gesture movement needs to travel before your handler is fired.

x: 100 px
y: 100 px

In this example, we've set the threshold to 1002 and made visible when that threshold is exceeded: when you start dragging the blue square, you'll see a ghost square showing how many pixels are left until the blue square starts moving per axis3.

function ThresholdExample() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ offset: [x, y] }) => api.start({ x, y }), {
threshold: 10
})
return <animated.div {...bind()} style={{ x, y }} />
}

If you still want your handler to be triggered for non intentional displacement, this is where the triggerAllEvents config option and the intentional state attribute become useful.

preventDefault

Type
boolean
Default
false

Will run event.cancelable && event.preventDefault() on all events triggered by the handler. Can be useful when dragging links or images.

function PreventDefault() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ offset: [x, y] }) => api.start({ x, y }), {
preventDefault: true,
filterTaps: true
})
return <animated.a href="https://github.com/pmndrs/use-gesture" {...bind()} style={{ x, y }} />
}

Don't forget to use the filterTaps option to prevent the link from opening unintentionally at the end of the gesture.

triggerAllEvents

Type
boolean
Default
false

Forces the handler to fire even for non intentional displacement (ignores the threshold). In that case, the intentional attribute from state will remain false until the threshold is reached.

bounds

Types
  • Bounds: { top?: number, bottom?: number, left?: number, right?: number }
  • (gestureState) => Bounds
Default
{ top: -Infinity, bottom: Infinity, left: -Infinity, right: Infinity }

If you want to set constraints to the user gesture, then you should use the bounds option. In that case, both the gesture movement and offset will be clamped to the specified bounds. bounds will be defaulted to Infinity when not set.

function BoundsExample() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ down, offset: [ox, oy] }) => api.start({ x: ox, y: oy, immediate: down }), {
bounds: { left: -100, right: 100, top: -50, bottom: 50 }
})
return <animated.div {...bind()} style={{ x, y }} />
}

scaleBounds and angleBounds serve the same purpose as bounds for the pinch gesture, in a {min,max} format.

Drag bounds

Types
  • DragBounds: Bounds | HTMLElement | React.RefObject<HTMLElement>
  • (gestureState) => DragBounds
Default
{ top: -Infinity, bottom: Infinity, left: -Infinity, right: Infinity }

Since v10 and for the drag gesture only, bounds can be a React ref or an HTMLElement, in which case the dragged element will be constrained to the element bounds (calculated with getBoundingClientRect).

scaleBounds

Types
  • scaleBounds: { min?: number, max?: number }
  • (gestureState) => scaleBounds
Default
{ min: -Infinity, max: Infinity }

angleBounds

Types
  • AngleBounds: { min?: number, max?: number }
  • (gestureState) => AngleBounds
Default
{ min: -Infinity, max: Infinity }

rubberband

Types
boolean | number | vector
Default
[0,0]

In some cases, you may want to simulate resistance when the user drags a component, for example when the end of a content is reached4.

You can set rubberband to true to use the default elasticity coeffecient of 0.15, or specify your own. The rubberband option also accepts a vector if you want to set different elasticity coeffecients per axis.

function RubberbandExample() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ down, offset: [ox, oy] }) => api.start({ x: ox, y: oy, immediate: down }), {
bounds: { left: -100, right: 100, top: -50, bottom: 50 },
rubberband: true
})
return <animated.div {...bind()} style={{ x, y }} />
}

Note that you have to set bounds for rubberbanding to take effect.

If you stop your gesture while being off-bounds, the offset or movement for the last event will be reverted to the closest bounds.

transform

Type
(xy: Vector2) => Vector2
Default
undefined

When you're interacting with canvas objects, you're dealing with space coordinates that aren't measured in pixels. In that case, you can tell πŸ‘Š @use-gesture to map screen values to the space with a transform function.

As you can see from the example below, we use the transform function to map the screen coordinates to THREE coordinates. Note that bounds or from values are expected to be expressed in the new space coordinates. Only threshold always refers to screen pixel values.

function Box() {
const { viewport } = useThree()
const { width, height, factor } = viewport
const [spring, setSpring] = useSpring(() => ({ position: [0, 0, 0], scale: [1, 1, 1] }))
const bind = useDrag(({ offset: [x, y] }) => setSpring({ position: [x, y, 0] }), {
// bounds are expressed in canvas coordinates!
bounds: { left: -width / 2, right: width / 2, top: -height / 2, bottom: height / 2 },
rubberband: true,
transform: ([x, y]) => [x / factor, -y / factor]
})
return (
<a3f.mesh {...bind()} {...spring}>
<boxBufferGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</a3f.mesh>
)
}
function Transform() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<Box />
</Canvas>
)
}

When you use the useGesture hook, you can set the transform option at the shared level and at the gesture level, with the transform set at the gesture level oveerriding the shared one.

useGesture({/* handlers */ }, {
transform: ([x, y]) => [x/2, y/2 ] // shared transform applies to all gestures
pinch: {
transform: xy => xy // specific pinch transform overrides shared
}
})

axis

xy gestures

Types
x | y | lock | undefined
Default
undefined

axis makes it easy to constraint the user gesture to a specific axis.

function AxisExample() {
const [{ x }, api] = useSpring(() => ({ x: 0 }))
const bind = useDrag(({ down, movement: [mx] }) => api.start({ x: down ? mx : 0 }), { axis: 'x' })
return <animated.div {...bind()} style={{ x }} />
}

From the code below it isn't obvious to understand why axis might be useful, since in any case the y movement isn't part of the logic.

But in reality axis does slightly more than just locking the gesture direction: if it detects that the user intent is to move the component in a different direction, it will stop firing the gesture handler. Here is an example to show the difference.

The component above can only move along the x axis. But try dragging and moving the component on the vertical axis. Without the axis option, you should notice the component movement will slightly jiggle horizontally because your movement won't be perfectly vertical.

'lock' option (pinch as well)

axis: 'lock' allows you to lock the movement of the gesture once a direction has been detected. In other words, if the user starts moving horizontally, the gesture will be locked on the x axis.

function LockAxisExample() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(
({ down, movement: [mx, my] }) => {
api.start({ x: down ? mx : 0, y: down ? my : 0, immediate: down })
},
{ axis: 'lock' }
)
return <animated.div {...bind()} style={{ x, y }} />
}

filterTaps (drag only)

Type
boolean
Default
false

Making a draggable component tappable or clickable can be tricky: differenciating a click from a drag is not always trivial. When you set filterTaps to true, the tap state attribute will be true on release if the total displacement is inferior to 3 pixels while down will remain false all along.

status: idle
function FilterTapsExample() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(
({ down, movement: [mx, my], tap }) => {
if (tap) alert('tap!')
api.start({ x: down ? mx : 0, y: down ? my : 0 })
},
{ filterTaps: true }
)
return <animated.div {...bind()} style={{ x, y }} />
}

If you still want your handler to be triggered for non intentional displacement, this is where the triggerAllEvents config option and the intentional state attribute become useful.

preventScroll (drag only)

Type
boolean
Default
false

This is an experimental feature, relevant for mobile devices.

touch-action: none is a common CSS property that you'll set on draggable items so that scroll doesn't interfere with the drag behavior on touch devices. However, this generally means that the scroll of the page can't be initiated from the draggable element. This is fine if your page isn't meant to be scrolled or if your draggable element is relatively small, but in case of large draggable areas this might become a usability issue.

preventScroll is a convenient way to have both vertical drag and vertical scrolling coexist. Note that scroll will always have precedence over drag. To drag vertically the user will have to press and hold the draggable area for 250ms (or the specified duration) without moving. After this duration, the element is draggable and scrolling is prevented. Note that if you drag horizontally the scroll will immediately be prevented without waiting for this duration.

On desktop, you should be able to drag the torus as you would expect without delay. On mobile, initiating scroll from the torus should let you scroll the page as expected. Hold down on the torus and you should be able to drag it after 250ms. This might be clunky as it's still under testing.

preventScrollAxis (drag only)

Types
x | y | xy
Default
y

This can optionally be used together with preventScroll. This defines the axis/axes in which scrolling is permitted, unless the user taps and holds on the element for the specified duration. Afterwhich, all scrolling is blocked. Depending on the complexity of the nesting of the element, you may need to assign the property touch-action: pan-x, touch-action: pan-y, or both, to the element to allow for the correct behavior.

pointer.touch (drag and pinch)

Type
boolean
Default
false

Most gestures, drag included, use pointer events. This works well in 99 situations in 100, but pointer events get canceled on touch devices when the user starts scrolling. Usually this is what you actually want, and the browser does it for you. But in some situations you may want the drag to persist while scrolling. In that case you'll need to indicate πŸ–– @use-gesture to use touch events, which aren't canceled on scroll.

pointer.capture (drag only)

Type
boolean
Default
true

By default, drag uses setPointerCapture to track the pointer movement. When a pointer is captured by a target, it won't trigger any listener from another target (even a CSS :hover). Most of the time this is fine, but in some situations, you may want to drag an element and still receive events from another target.

pointer.captureΒ β†’Β 
Connect the pink dot to the blue dot

In the example above, the blue dot listens for hover events: when the pink dot is being dragged, it will attach to it. You'll notice that when pointer.capture is set to true, you won't be able to connect the dots. That's because the blue dot doesn't get pointer events when the pink dot capture the events. Setting pointer.capture to false solves this problem.

Note that this example doesn't work on mobile and is just here to illustrate the option. If you'd want to actually do this in a way that works on mobile, you'd probably have to use document.elementAtPoint. Look at this sandbox.

pointer.lock (drag only)

Type
boolean
Default
false

Set lock to true and the drag gesture will requestPointerLock when the drag starts and exitPointerLock when the drag gesture ends. This feature is only relevant on non-touch devices.

In the example above, the cursor will be hidden when the drag starts and the square will "loop" horizontally and vertically inside its parent element.

delay (drag only)

Types
boolean | number
Default
0

delay delays the drag gesture for the amount of milliseconds you specify. This might be useful if you don't want your logic to fire right away. The below example has a delay set to 1000. Try clicking on the square without moving your mouse.

1000ms before drag starts

Note that if the the pointer is moved by the user, the drag gesture will fire immediately without waiting for the delay.

function DelayExample() {
const [{ x, y, scale }, api] = useSpring(() => ({ x: 0, y: 0, scale: 1 }))
const bind = useDrag(
({ down, movement: [mx, my] }) => api.start({ x: down ? mx : 0, y: down ? my : 0, scale: down ? 1.2 : 1 }),
{ delay: 1000 }
)
return <animated.div {...bind()} style={{ x, y, scale }} />
}

Note that delay and threshold don't play well together: without moving your pointer, your handler will never get triggered.

swipe.distance (drag only)

Types
number | vector
Default
[50,50]

See the swipe state attribute for more.

swipe.velocity (drag only)

Types
number | vector
Default
[0.5,0.5]

See the swipe state attribute for more.

swipe.duration (drag only)

Type
number
Default
250

A drag gesture lasting moore than swipe.duration (in milliseconds) will never be considered a swipe. See the swipe state attribute for more.


  1. If you're used to πŸ€™ @use-gesture, this was the most common usecase for memo.↩
  2. This is a bit extreme in actual use cases you would be closer to 20.↩
  3. As you might have noticed from the example above, threshold works per axis: if the gesture exceeds the threshold value horizontally, you will get updates for horizontal displacement, but vertical threshold will have to be reached before vertical displacement is registered.↩
  4. Have a look at this article for more details about building mobile interfaces.↩