Skip to content

useDrag

Feature-rich Drag element hook

Components that use this hook: <SlideAction>, <Dialog> mobile variant

Import

import {useDrag} from '@kaiverse/k/hooks'

Basic

Drag the block below
😀

Direction

Available options: 'vertical', 'horizontal', 'both'.
Default: 'both'.

Horizontal only
Vertical only
direction.tsx
useDrag({direction: ...})

Step

Move the target element by a fixed amount of pixels. The stepSize option can be a number for both directions or an object {x: number, y: number}.

With {"stepSize":50}
Drag the block below
😀
stepSize.tsx
useDrag({stepSize: ...})

Limit

Limit the draggable area by using the limit or relativeLimit option.

Drag the block below
limitBoxSize: {"width":"300px","height":"150px"}
targetSize: {"width":"100px","height":"50px"}
limit: {"x":{"min":0,"max":200},"y":{"min":0,"max":100}}

Limit box

👋 drag me
limit.tsx
useDrag({
limit: {...}
})

Relative Limit

You can limit the dragging area relative to the direct parent element by using the relativeLimit option. This option will be ignored if limit is provided.

Limit the dragging distance by:

  • "client-size" recommended : parent element’s client size (includes padding but excludes borders, margins).
    DO NOT use this option when parent is an inline element.
  • "client-no-padding" recommended : same as "client-size" but excludes padding.
  • "offset-size": parent element’s offset size (including borders, padding).
  • "offset-no-padding": same as "offset-size" but excludes padding.

Confused? Let’s put it another way:

OptionsDescription
"client-*"Normal limit the dragging area.
"offset-*"Ignore parent border-width - can be dragged over parent’s border area.
"*-no-padding"Ignore the parent padding area - can be dragged over it.

Playground

Try to drag the block below

👋 drag me
Playground related styles:
{ "padding": "2rem", "borderWidth": "16px" }
: Border
relative-limit.tsx
useDrag({
relativeLimit: '...',
})

decelerationEffect

Allow dragging over the limit, and enable deceleration effect. After releasing (pointercancel or pointerup event), the target element will move back to the nearest limit position.

Try dragging the element over the limit area.

limitBoxSize: {"width":"300px","height":"150px"}
targetSize: {"width":"100px","height":"50px"}
limit: {"x":{"min":0,"max":200},"y":{"min":0,"max":100}}

Limit box

👋 drag me
deceleration-effect.tsx
useDrag({
decelerationEffect: true,
})

Special Types

NameTypeDescription
UseDragPosition{x: number; y: number}Position parameters
UseDragSetPosition{position: UseDragPosition, options?: ...}
options type: See setPosition Options section
Update position.
UseDragRelativeLimit"client-size" | "client-no-padding" | "offset-size" | "offset-no-padding"relativeLimit options.
UseDragOptionsSee useDrag Options belowAll useDrag options.

setPosition Options

NameTypeDefaultDescription
transitionstring | trueCSS transition for the position change. See transition section for more info.
skipCalulateStepbooleanfalseIf stepSize > 0 and you wanna manually update the position, consider set this option to true to skip the default calculation.
shouldUpdatePositionStatebooleanfalseEnable this flag to allow updating the returned position state and manually handle the transform animation, please note that your component will be re-rendered.
By default, the hook will handle the calculation for you, and your component won’t re-render.

transition

Apply the transition to the target element once, for that specific position update - setPosition only.

If you set transition to true, it will apply the default transition. You can also provide a custom transition style by passing a string value.

useDrag Options

NameTypeDefaultDescription
targetRefRefObject<T>If provided, this hook will track and make this ref element draggable instead of its own internal target ref (and also won’t return it anymore).
direction'vertical' | 'horizontal' | 'both''both'Dragging direction.
limit{x?: {max?: number; min?: number}, y?: {max?: number; min?: number}}Limit the draggable area. See Limit section.
relativeLimitUseDragRelativeLimitLimit dragging distance relatively to the direct parent element. Will be ignored if limit is provided. See Relative Limit section.
decelerationEffectbooleanfalseAllow dragging over the limit, and enable deceleration effect. After releasing (pointercancel or pointerup event), the target element will move back to the nearest limit position.
Only available when limit or relativeLimit is provided.
stepSizenumber | UseDragPosition0Position step size.
addBrowserHintStylesbooleantrueBy default, the hook will add will-change: transform and touch-action: none to the target element.
onStart(target: RefObject<T>, position: UseDragPosition, setPosition: UseDragSetPosition) => voidCallback when dragging starts.
onMove(target: RefObject<T>, position: UseDragPosition) => voidCallback when dragging.
onEnd(target: RefObject<T>, position: UseDragPosition, setPosition: UseDragSetPosition) => voidCallback when dragging ends.
ignorePointerCancelbooleanfalseIf true, ignore the pointercancel event.
touchbooleantrueShould listening touch events or not.
Side note: This hook treats pointer events caused by a pen or stylus device (pointerType === 'pen') same as touch events.
mousebooleantrueShould listening mouse’s main button events (usually left-click) or not.
disabledbooleanfalseIf true, the target element won’t draggable anymore. Equivalent to touch === false && mouse === false

Footgun Flags

Easier to shoot yourself in the foot with these options.

NameTypeDefaultDescription
manualStylingOnMovebooleanfalseSet to true to allow update position state onMove and manually handle styling such as CSS transform.
Note: Your component will re-render on every step move.
returnedPositionDebounceTimenumber0Enable debouncing for the setPosition setter in millisecond (ms). Used for manually handling the moving transform animation (manualStylingOnMove = true) by using returned position state instead of the onMove event.
It doesn’t affect the default onMove transform animation (manualStylingOnMove = false).