useDrag
Track a pointer drag on an element, and report how far it has moved.
SourceThe hook returns a ref callback. Put it on the element the drag starts from, and the handlers do the rest — there is no state to hold and nothing to clean up.
function App() { const [position, setPosition] = useState({ x: 0, y: 0 }) const [delta, setDelta] = useState({ x: 0, y: 0 }) const [dragging, setDragging] = useState(false) const dragRef = useDrag<HTMLDivElement>({ cursor: 'grabbing', onDragStart: () => setDragging(true), onDragEnd: () => setDragging(false), onDrag: (x, y, deltaX, deltaY) => { setPosition({ x, y }) setDelta({ x: deltaX, y: deltaY }) }, }) return ( <div style={{ userSelect: 'none' }}> <div ref={dragRef} style={{ padding: '2rem', marginBottom: '0.5rem', border: '1px solid currentColor', borderRadius: 8, textAlign: 'center', touchAction: 'none', }} > Drag here </div> <div>dragging: {String(dragging)}</div> <div> x: {position.x}, y: {position.y} </div> <div> deltaX: {delta.x}, deltaY: {delta.y} </div> </div> ) }
Import
import { useDrag } from '@tremolo-ui/react'
What it handles for you
The pointer is captured, so the drag keeps going outside the element.
A drag that leaves the box, the window, or the page still reports, and still
ends on the release. Listening for pointermove on the element itself gives up
as soon as the pointer leaves it.
threshold keeps a click from counting as a drag. A click moves the
pointer by a pixel or two; without a threshold the two presses of a double
click report a drag between them. Movement below the threshold is carried over
rather than discarded, so a slow drag still reports once it adds up.
onDragStart fires when the drag starts, not on pointerdown. It is the
moment the threshold is passed, which is also when cursor is applied.
Set touch-action: none on the element in CSS. Without it the browser scrolls
the page instead of letting the drag through.
Reading the modifier keys
Every handler is given the whole DragState as its last argument, and the
pointer event is on it. That is where the modifier keys are:
useDrag({
onDrag: (x, y, deltaX, deltaY, state) => {
const step = state.event.shiftKey ? 0.1 : 1
},
})
Moving a value rather than a pointer
useDrag reports pixels. When what you want is a number with a range, a step
and a scale, use useDragValue instead — it is
built on this hook and does the mapping.
Options
| Prop | Type | Default |
|---|---|---|
threshold | number | 1 |
cursor | string | — |
pointerLock | boolean | false |
onDrag | (x: number, y: number, deltaX: number, deltaY: number, state: DragState) => void | — |
onDragStart | (state: DragState) => void | — |
onDragEnd | (state: DragState) => void | — |
Full reference: UseDragOptions