Gesture options
👈 React UseGesture offers different options to configure the gestures.
Some options are generic to the way 👆 React UseGesture 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, you'll need to structure the config option object differently.
// when you use a gesture-specific hookuseDrag(state => doSomethingWith(state), { ...genericOptions, ...dragOptions })// when you use the useGesture hookuseGesture(state => doSomethingWith(state), {// global options such as `domTarget`...genericOptions,// gesture specific optionsdrag: dragOptions,wheel: wheelOptions,pinch: pinchOptions,scroll: scrollOptions,wheel: wheelOptions,hover: hoverOptions,})
Generic options
Generic options deal with how 👇 React UseGesture will set event listeners.
Option | Description |
---|---|
domTarget | Lets you specify a dom node or React ref you want to attach the gesture to. |
eventOptions | Lets you customize if you want events to be passive or captured. |
window | Lets you specify which window element the gesture should bind events to (only relevant for the drag gesture). |
enabled | When 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.
Options | Gestures | Description |
---|---|---|
enabled | all | Whether the gesture is enabled. |
initial | all | The initial position movement should start from. |
threshold | all | The handler will fire only when the gesture displacement is greater than the threshold. |
triggerAllEvents | all | 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. |
axis | xy | Your handler will only trigger if a movement is detected on the specified axis. |
lockDirection | xy | If true , the gesture will lock the first detected direction. |
bounds | xy | Limits the gesture movement and offset to the specified bounds. |
distanceBounds | pinch | Limits the distance movement and offset to the specified bounds. |
angleBounds | pinch | Limits the angle movement and offset to the specified bounds. |
rubberband | all | The elasticity coefficient of the gesture when going out of bounds. When set to true , the elasticiy coefficient will be defaulted to 0.15 |
transform | all | A function that you can use to transform pointer values. Useful to map your screen coordinates to custom space coordinates such as a canvas. |
filterTaps | drag | If true , the component won't trigger your drag logic if the user just clicked on the component. |
experimental_preventWindowScrollY | drag | If true , drag will be triggered after 250ms and will prevent window scrolling. |
useTouch | drag | If true , drag will use touch events on touch-enabled devices. Read more below. |
delay | drag | If 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 . |
swipeDistance | drag | The minimum distance per axis (in pixels ) the drag gesture needs to travel to trigger a swipe. |
swipeVelocity | drag | The minimum velocity per axis (in pixels / ms ) the drag gesture needs to reach before the pointer is released. |
swipeDuration | drag | The maximum duration in milliseconds that a swipe is detected. |
Options explained
domTarget
✊ React UseGesture also 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.scrollHeightuseScroll(({ xy: [, y] }) => api.start({ width: (y / height) * 100 + '%' }), { domTarget: 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 domTarget
. 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 divuseScroll(({ event }) => event.preventDefault(), {domTarget: myRef,eventOptions: { passive: false },})return <div ref={myRef} />
eventOptions
When eventOptions.capture
is set to true
, events will be captured.
eventOptions.passive
sets whether events are passive. Note that if you want events not to be passive, you will need to attach events directly to a node using domTarget
because of the way React handles events.
window
Lets you specify which window element the gesture should bind events to (only relevant for the drag
gesture).
enabled
Whether the gesture is enabled.
initial
Everytime a gesture starts, the movement
state attribute is set to [0,0]
. But in some cases, you might want to calculate movement
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 movement
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, movement: [mx] }) => api.start({ x: down ? mx : 0, immediate: down, config: { duration: 3000 } }),{ initial: () => [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
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.
In this example, we've set the threshold
to 100
2 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 theintentional
state attribute become useful.
triggerAllEvents
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
- Bounds: { top?: number, bottom?: number, left?: number, right?: number }
- (gestureState) => Bounds
If you want to set contraints 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 }} />}
distanceBounds
andangleBounds
serve the same purpose asbounds
for thepinch
gesture, in a{min,max}
format.
distanceBounds
- DistanceBounds: { min?: number, max?: number }
- (gestureState) => DistanceBounds
angleBounds
- AngleBounds: { min?: number, max?: number }
- (gestureState) => AngleBounds
rubberband
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
ormovement
for the last event will be reverted to the closest bounds.
transform
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 🤙 React UseGesture 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 initial
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 } = viewportconst [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 thetransform
option at the shared level and at the gesture level, with thetransform
set at the gesture level oveerriding the shared one.
useGesture({/* handlers */ }, {transform: ([x, y]) => [x/2, y/2 ] // shared transform applies to all gesturespinch: {transform: xy => xy // specific pinch transform overrides shared}})
axis (xy gestures only)
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.
lockDirection (xy gestures only)
lockDirection
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 LockDirectionExample() {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 })},{ lockDirection: true })return <animated.div {...bind()} style={{ x, y }} />}
filterTaps (drag only)
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.
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 theintentional
state attribute become useful.
experimental_preventWindowScrollY (drag only)
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.
experimental_preventWindowScrollY
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 the draggable area for 250ms
without moving. After these 250ms
the element is draggable and scroll is prevented. Note that if you drag horizontally the scroll will immediately be prevented without waiting for 250ms
.
On desktop, you should be able to drag the torus as you would expect. On mobile, initiating the scroll from the torus should let you scroll the page as expected. Hold touch and drag and you should be able to drag the canvas. This might be clunky as still under test.
useTouch (drag only)
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 🖐 React UseGesture to use touch events, which aren't canceled on scroll.
delay (drag only)
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.
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
andthreshold
don't play well together: without moving your pointer, your handler will never get triggered.
swipeDistance (drag only)
See the swipe
state attribute for more.
swipeVelocity (drag only)
See the swipe
state attribute for more.
swipeDuration (drag only)
A drag gesture lasting moore than swipeDuration
(in milliseconds) will never be considered a swipe. See the swipe
state attribute for more.
- If you're used to 🤘 React UseGesture, this was the most common usecase for
memo
.↩ - This is a bit extreme in actual use cases you would be closer to
20
.↩ - 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.↩ - Have a look at this article for more details about building mobile interfaces.↩