📄 useDraggable¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 14 |
| 📦 Imports | 13 |
| 📊 Variables & Constants | 1 |
| 🟢 Vue Composition API | 3 |
| 📐 Interfaces | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/core/useDraggable/index.ts
📦 Imports¶
| Name | Source |
|---|---|
ComputedRef |
vue |
MaybeRefOrGetter |
vue |
Ref |
vue |
PointerType |
../types |
Position |
../types |
isClient |
@vueuse/shared |
toRefs |
@vueuse/shared |
computed |
vue |
deepRef |
vue |
toValue |
vue |
watch |
vue |
defaultWindow |
../_configurable |
useEventListener |
../useEventListener |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
defaultScrollConfig |
{ speed: number; margin: number; dire... |
const | { speed: 2, margin: 30, direction: 'both' } |
✗ |
Vue Composition API¶
| Name | Type | Reactive Variables | Composables |
|---|---|---|---|
watch |
watch | none | none |
computed |
computed | none | none |
computed |
computed | none | none |
Functions¶
useDraggable(target: MaybeRefOrGetter<HTMLElement | SVGEleme…, options: UseDraggableOptions): UseDraggableReturn¶
Make elements draggable.
Parameters:
targetany: No descriptionoptionsany: No description
See: https://vueuse.org/useDraggable
Raw JSDoc
Calls:
deepRef (from vue)toValue (from vue)pointerTypes.includese.preventDefaulte.stopPropagationgetScrollAxisValuescontainer.scrollBysetIntervaltoValue(target)!.getBoundingClientRecthandleAutoScrollclearIntervalisPointerNearEdgestartAutoScrollstopAutoScrollwatch (from vue)toValue(buttons).includesfilterEventcontainer?.getBoundingClientRectonStarthandleEventclampContainerScrollMath.minMath.maxonMoveonEnduseEventListener (from ../useEventListener)toRefs (from @vueuse/shared)computed (from vue)
Code
export function useDraggable(
target: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>,
options: UseDraggableOptions = {},
): UseDraggableReturn {
const {
pointerTypes,
preventDefault,
stopPropagation,
exact,
onMove,
onEnd,
onStart,
initialValue,
axis = 'both',
draggingElement = defaultWindow,
containerElement,
handle: draggingHandle = target,
buttons = [0],
restrictInView,
autoScroll = false,
} = options
const position = deepRef<Position>(
toValue(initialValue) ?? { x: 0, y: 0 },
)
const pressedDelta = deepRef<Position>()
const filterEvent = (e: PointerEvent) => {
if (pointerTypes)
return pointerTypes.includes(e.pointerType as PointerType)
return true
}
const handleEvent = (e: PointerEvent) => {
if (toValue(preventDefault))
e.preventDefault()
if (toValue(stopPropagation))
e.stopPropagation()
}
const scrollConfig = toValue(autoScroll)
const scrollSettings = typeof scrollConfig === 'object'
? {
speed: toValue(scrollConfig.speed) ?? defaultScrollConfig.speed,
margin: toValue(scrollConfig.margin) ?? defaultScrollConfig.margin,
direction: scrollConfig.direction ?? defaultScrollConfig.direction,
}
: defaultScrollConfig
const getScrollAxisValues = (value: number | Position): [number, number] =>
typeof value === 'number' ? [value, value] : [value.x, value.y]
const handleAutoScroll = (
container: HTMLElement | SVGElement,
targetRect: DOMRect,
position: Position,
) => {
const { clientWidth, clientHeight, scrollLeft, scrollTop, scrollWidth, scrollHeight } = container
const [marginX, marginY] = getScrollAxisValues(scrollSettings.margin)
const [speedX, speedY] = getScrollAxisValues(scrollSettings.speed)
let deltaX = 0
let deltaY = 0
if (scrollSettings.direction === 'x' || scrollSettings.direction === 'both') {
if (position.x < marginX && scrollLeft > 0)
deltaX = -speedX
else if (position.x + targetRect.width > clientWidth - marginX && scrollLeft < scrollWidth - clientWidth)
deltaX = speedX
}
if (scrollSettings.direction === 'y' || scrollSettings.direction === 'both') {
if (position.y < marginY && scrollTop > 0)
deltaY = -speedY
else if (position.y + targetRect.height > clientHeight - marginY && scrollTop < scrollHeight - clientHeight)
deltaY = speedY
}
if (deltaX || deltaY) {
container.scrollBy({ left: deltaX, top: deltaY, behavior: 'auto' })
}
}
let autoScrollInterval: ReturnType<typeof setInterval> | null = null
const startAutoScroll = () => {
const container = toValue(containerElement)
if (container && !autoScrollInterval) {
autoScrollInterval = setInterval(() => {
const targetRect = toValue(target)!.getBoundingClientRect()
const { x, y } = position.value
const relativePosition = { x: x - container.scrollLeft, y: y - container.scrollTop }
if (relativePosition.x >= 0 && relativePosition.y >= 0) {
handleAutoScroll(
container,
targetRect,
relativePosition,
)
relativePosition.x += container.scrollLeft
relativePosition.y += container.scrollTop
position.value = relativePosition
}
}, 1000 / 60)
}
}
const stopAutoScroll = () => {
if (autoScrollInterval) {
clearInterval(autoScrollInterval)
autoScrollInterval = null
}
}
const isPointerNearEdge = (
pointer: Position,
container: HTMLElement | SVGElement,
margin: number | Position,
targetRect: DOMRect,
) => {
const [marginX, marginY] = typeof margin === 'number' ? [margin, margin] : [margin.x, margin.y]
const { clientWidth, clientHeight } = container
return (
pointer.x < marginX
|| pointer.x + targetRect.width > clientWidth - marginX
|| pointer.y < marginY
|| pointer.y + targetRect.height > clientHeight - marginY
)
}
const checkAutoScroll = () => {
if (toValue(options.disabled) || !pressedDelta.value)
return
const container = toValue(containerElement)
if (!container)
return
const targetRect = toValue(target)!.getBoundingClientRect()
const { x, y } = position.value
const relativePosition = { x: x - container.scrollLeft, y: y - container.scrollTop }
if (isPointerNearEdge(relativePosition, container, scrollSettings.margin, targetRect))
startAutoScroll()
else stopAutoScroll()
}
if (toValue(autoScroll)) {
watch(position, checkAutoScroll)
}
const start = (e: PointerEvent) => {
if (!toValue(buttons).includes(e.button))
return
if (toValue(options.disabled) || !filterEvent(e))
return
if (toValue(exact) && e.target !== toValue(target))
return
const container = toValue(containerElement)
const containerRect = container?.getBoundingClientRect?.()
const targetRect = toValue(target)!.getBoundingClientRect()
const pos = {
x: e.clientX - (container ? targetRect.left - containerRect!.left + (autoScroll ? 0 : container.scrollLeft) : targetRect.left),
y: e.clientY - (container ? targetRect.top - containerRect!.top + (autoScroll ? 0 : container.scrollTop) : targetRect.top),
}
if (onStart?.(pos, e) === false)
return
pressedDelta.value = pos
handleEvent(e)
}
const move = (e: PointerEvent) => {
if (toValue(options.disabled) || !filterEvent(e))
return
if (!pressedDelta.value)
return
const container = toValue(containerElement)
if (container instanceof HTMLElement)
clampContainerScroll(container)
const targetRect = toValue(target)!.getBoundingClientRect()
let { x, y } = position.value
if (axis === 'x' || axis === 'both') {
x = e.clientX - pressedDelta.value.x
if (container)
x = Math.min(Math.max(0, x), container.scrollWidth - targetRect!.width)
}
if (axis === 'y' || axis === 'both') {
y = e.clientY - pressedDelta.value.y
if (container)
y = Math.min(Math.max(0, y), container.scrollHeight - targetRect!.height)
}
if (toValue(autoScroll) && container) {
if (autoScrollInterval === null)
handleAutoScroll(container, targetRect, { x, y })
x += container.scrollLeft
y += container.scrollTop
}
if (container && (restrictInView || autoScroll)) {
if (axis !== 'y') {
const relativeX = x - container.scrollLeft
if (relativeX < 0)
x = container.scrollLeft
else if (relativeX > container.clientWidth - targetRect.width)
x = container.clientWidth - targetRect.width + container.scrollLeft
}
if (axis !== 'x') {
const relativeY = y - container.scrollTop
if (relativeY < 0)
y = container.scrollTop
else if (relativeY > container.clientHeight - targetRect.height)
y = container.clientHeight - targetRect.height + container.scrollTop
}
}
position.value = {
x,
y,
}
onMove?.(position.value, e)
handleEvent(e)
}
const end = (e: PointerEvent) => {
if (toValue(options.disabled) || !filterEvent(e))
return
if (!pressedDelta.value)
return
pressedDelta.value = undefined
if (autoScroll)
stopAutoScroll()
onEnd?.(position.value, e)
handleEvent(e)
}
if (isClient) {
const config = () => ({
capture: options.capture ?? true,
passive: !toValue(preventDefault),
})
useEventListener(draggingHandle, 'pointerdown', start, config)
useEventListener(draggingElement, 'pointermove', move, config)
useEventListener(draggingElement, ['pointerup', 'pointercancel'], end, config)
}
return {
...toRefs(position),
position,
isDragging: computed(() => !!pressedDelta.value),
style: computed(() => `
left: ${position.value.x}px;
top: ${position.value.y}px;
${autoScroll ? 'text-wrap: nowrap;' : ''}
`),
}
}
clampContainerScroll(container: HTMLElement): void¶
Parameters:
containerHTMLElement
Returns: void
Calls:
Math.max
Code
function clampContainerScroll(container: HTMLElement) {
if (container.scrollLeft > container.scrollWidth - container.clientWidth)
container.scrollLeft = Math.max(0, container.scrollWidth - container.clientWidth)
if (container.scrollTop > container.scrollHeight - container.clientHeight)
container.scrollTop = Math.max(0, container.scrollHeight - container.clientHeight)
}
Internal helpers¶
Declared inside another function in this file.
filterEvent(e: PointerEvent): boolean¶
Parameters:
ePointerEvent
Returns: boolean
Calls:
pointerTypes.includes
Code
handleEvent(e: PointerEvent): void¶
Parameters:
ePointerEvent
Returns: void
Calls:
toValue (from vue)e.preventDefaulte.stopPropagation
Code
getScrollAxisValues(value: number | Position): [number, number]¶
Parameters:
valuenumber | Position
Returns: [number, number]
Code
handleAutoScroll(container: HTMLElement | SVGElement, targetRect: DOMRect, position: Position): void¶
Parameters:
containerHTMLElement | SVGElementtargetRectDOMRectpositionPosition
Returns: void
Calls:
getScrollAxisValuescontainer.scrollBy
Code
(
container: HTMLElement | SVGElement,
targetRect: DOMRect,
position: Position,
) => {
const { clientWidth, clientHeight, scrollLeft, scrollTop, scrollWidth, scrollHeight } = container
const [marginX, marginY] = getScrollAxisValues(scrollSettings.margin)
const [speedX, speedY] = getScrollAxisValues(scrollSettings.speed)
let deltaX = 0
let deltaY = 0
if (scrollSettings.direction === 'x' || scrollSettings.direction === 'both') {
if (position.x < marginX && scrollLeft > 0)
deltaX = -speedX
else if (position.x + targetRect.width > clientWidth - marginX && scrollLeft < scrollWidth - clientWidth)
deltaX = speedX
}
if (scrollSettings.direction === 'y' || scrollSettings.direction === 'both') {
if (position.y < marginY && scrollTop > 0)
deltaY = -speedY
else if (position.y + targetRect.height > clientHeight - marginY && scrollTop < scrollHeight - clientHeight)
deltaY = speedY
}
if (deltaX || deltaY) {
container.scrollBy({ left: deltaX, top: deltaY, behavior: 'auto' })
}
}
startAutoScroll(): void¶
Returns: void
Calls:
toValue (from vue)setIntervaltoValue(target)!.getBoundingClientRecthandleAutoScroll
Code
() => {
const container = toValue(containerElement)
if (container && !autoScrollInterval) {
autoScrollInterval = setInterval(() => {
const targetRect = toValue(target)!.getBoundingClientRect()
const { x, y } = position.value
const relativePosition = { x: x - container.scrollLeft, y: y - container.scrollTop }
if (relativePosition.x >= 0 && relativePosition.y >= 0) {
handleAutoScroll(
container,
targetRect,
relativePosition,
)
relativePosition.x += container.scrollLeft
relativePosition.y += container.scrollTop
position.value = relativePosition
}
}, 1000 / 60)
}
}
stopAutoScroll(): void¶
Returns: void
Calls:
clearInterval
Code
isPointerNearEdge(pointer: Position, container: HTMLElement | SVGElement, margin: number | Position, targetRect: DOMRect): boolean¶
Parameters:
pointerPositioncontainerHTMLElement | SVGElementmarginnumber | PositiontargetRectDOMRect
Returns: boolean
Code
(
pointer: Position,
container: HTMLElement | SVGElement,
margin: number | Position,
targetRect: DOMRect,
) => {
const [marginX, marginY] = typeof margin === 'number' ? [margin, margin] : [margin.x, margin.y]
const { clientWidth, clientHeight } = container
return (
pointer.x < marginX
|| pointer.x + targetRect.width > clientWidth - marginX
|| pointer.y < marginY
|| pointer.y + targetRect.height > clientHeight - marginY
)
}
checkAutoScroll(): void¶
Returns: void
Calls:
toValue (from vue)toValue(target)!.getBoundingClientRectisPointerNearEdgestartAutoScrollstopAutoScroll
Code
() => {
if (toValue(options.disabled) || !pressedDelta.value)
return
const container = toValue(containerElement)
if (!container)
return
const targetRect = toValue(target)!.getBoundingClientRect()
const { x, y } = position.value
const relativePosition = { x: x - container.scrollLeft, y: y - container.scrollTop }
if (isPointerNearEdge(relativePosition, container, scrollSettings.margin, targetRect))
startAutoScroll()
else stopAutoScroll()
}
start(e: PointerEvent): void¶
Parameters:
ePointerEvent
Returns: void
Calls:
toValue(buttons).includestoValue (from vue)filterEventcontainer?.getBoundingClientRecttoValue(target)!.getBoundingClientRectonStarthandleEvent
Code
(e: PointerEvent) => {
if (!toValue(buttons).includes(e.button))
return
if (toValue(options.disabled) || !filterEvent(e))
return
if (toValue(exact) && e.target !== toValue(target))
return
const container = toValue(containerElement)
const containerRect = container?.getBoundingClientRect?.()
const targetRect = toValue(target)!.getBoundingClientRect()
const pos = {
x: e.clientX - (container ? targetRect.left - containerRect!.left + (autoScroll ? 0 : container.scrollLeft) : targetRect.left),
y: e.clientY - (container ? targetRect.top - containerRect!.top + (autoScroll ? 0 : container.scrollTop) : targetRect.top),
}
if (onStart?.(pos, e) === false)
return
pressedDelta.value = pos
handleEvent(e)
}
move(e: PointerEvent): void¶
Parameters:
ePointerEvent
Returns: void
Calls:
toValue (from vue)filterEventclampContainerScrolltoValue(target)!.getBoundingClientRectMath.minMath.maxhandleAutoScrollonMovehandleEvent
Code
(e: PointerEvent) => {
if (toValue(options.disabled) || !filterEvent(e))
return
if (!pressedDelta.value)
return
const container = toValue(containerElement)
if (container instanceof HTMLElement)
clampContainerScroll(container)
const targetRect = toValue(target)!.getBoundingClientRect()
let { x, y } = position.value
if (axis === 'x' || axis === 'both') {
x = e.clientX - pressedDelta.value.x
if (container)
x = Math.min(Math.max(0, x), container.scrollWidth - targetRect!.width)
}
if (axis === 'y' || axis === 'both') {
y = e.clientY - pressedDelta.value.y
if (container)
y = Math.min(Math.max(0, y), container.scrollHeight - targetRect!.height)
}
if (toValue(autoScroll) && container) {
if (autoScrollInterval === null)
handleAutoScroll(container, targetRect, { x, y })
x += container.scrollLeft
y += container.scrollTop
}
if (container && (restrictInView || autoScroll)) {
if (axis !== 'y') {
const relativeX = x - container.scrollLeft
if (relativeX < 0)
x = container.scrollLeft
else if (relativeX > container.clientWidth - targetRect.width)
x = container.clientWidth - targetRect.width + container.scrollLeft
}
if (axis !== 'x') {
const relativeY = y - container.scrollTop
if (relativeY < 0)
y = container.scrollTop
else if (relativeY > container.clientHeight - targetRect.height)
y = container.clientHeight - targetRect.height + container.scrollTop
}
}
position.value = {
x,
y,
}
onMove?.(position.value, e)
handleEvent(e)
}
end(e: PointerEvent): void¶
Parameters:
ePointerEvent
Returns: void
Calls:
toValue (from vue)filterEventstopAutoScrollonEndhandleEvent
Code
config(): { capture: boolean; passive: boolean; }¶
Returns: { capture: boolean; passive: boolean; }
Interfaces¶
UseDraggableOptions¶
Interface Code
export interface UseDraggableOptions {
/**
* Only start the dragging when click on the element directly
*
* @default false
*/
exact?: MaybeRefOrGetter<boolean>
/**
* Prevent events defaults
*
* @default false
*/
preventDefault?: MaybeRefOrGetter<boolean>
/**
* Prevent events propagation
*
* @default false
*/
stopPropagation?: MaybeRefOrGetter<boolean>
/**
* Whether dispatch events in capturing phase
*
* @default true
*/
capture?: boolean
/**
* Element to attach `pointermove` and `pointerup` events to.
*
* @default window
*/
draggingElement?: MaybeRefOrGetter<HTMLElement | SVGElement | Window | Document | null | undefined>
/**
* Element for calculating bounds (If not set, it will use the event's target).
*
* @default undefined
*/
containerElement?: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>
/**
* Handle that triggers the drag event
*
* @default target
*/
handle?: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>
/**
* Pointer types that listen to.
*
* @default ['mouse', 'touch', 'pen']
*/
pointerTypes?: PointerType[]
/**
* Initial position of the element.
*
* @default { x: 0, y: 0 }
*/
initialValue?: MaybeRefOrGetter<Position>
/**
* Callback when the dragging starts. Return `false` to prevent dragging.
*/
onStart?: (position: Position, event: PointerEvent) => void | false
/**
* Callback during dragging.
*/
onMove?: (position: Position, event: PointerEvent) => void
/**
* Callback when dragging end.
*/
onEnd?: (position: Position, event: PointerEvent) => void
/**
* Axis to drag on.
*
* @default 'both'
*/
axis?: 'x' | 'y' | 'both'
/**
* Disabled drag and drop.
*
* @default false
*/
disabled?: MaybeRefOrGetter<boolean>
/**
* Mouse buttons that are allowed to trigger drag events.
*
* - `0`: Main button, usually the left button or the un-initialized state
* - `1`: Auxiliary button, usually the wheel button or the middle button (if present)
* - `2`: Secondary button, usually the right button
* - `3`: Fourth button, typically the Browser Back button
* - `4`: Fifth button, typically the Browser Forward button
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button#value
* @default [0]
*/
buttons?: MaybeRefOrGetter<number[]>
/**
* Whether to restrict dragging within the visible area of the container.
*
* If enabled, the draggable element will not leave the visible area of its container,
* ensuring it remains within the viewport of the container during the drag.
*
* @default false
*/
restrictInView?: MaybeRefOrGetter<boolean>
/**
* Whether to enable auto-scroll when dragging near the edges.
*
* @default false
*/
autoScroll?: MaybeRefOrGetter<boolean | {
/**
* Speed of auto-scroll.
*
* @default 2
*/
speed?: MaybeRefOrGetter<number | Position>
/**
* Margin from the edge to trigger auto-scroll.
*
* @default 30
*/
margin?: MaybeRefOrGetter<number | Position>
/**
* Direction of auto-scroll.
*
* @default 'both'
*/
direction?: 'x' | 'y' | 'both'
}>
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
exact |
MaybeRefOrGetter<boolean> |
✓ | not shown |
preventDefault |
MaybeRefOrGetter<boolean> |
✓ | not shown |
stopPropagation |
MaybeRefOrGetter<boolean> |
✓ | not shown |
capture |
boolean |
✓ | not shown |
draggingElement |
MaybeRefOrGetter<HTMLElement \| SVGElement \| Window \| Document \| null \| u... |
✓ | not shown |
containerElement |
MaybeRefOrGetter<HTMLElement \| SVGElement \| null \| undefined> |
✓ | not shown |
handle |
MaybeRefOrGetter<HTMLElement \| SVGElement \| null \| undefined> |
✓ | not shown |
pointerTypes |
PointerType[] |
✓ | not shown |
initialValue |
MaybeRefOrGetter<Position> |
✓ | not shown |
onStart |
(position: Position, event: PointerEvent) => void \| false |
✓ | not shown |
onMove |
(position: Position, event: PointerEvent) => void |
✓ | not shown |
onEnd |
(position: Position, event: PointerEvent) => void |
✓ | not shown |
axis |
'x' \| 'y' \| 'both' |
✓ | not shown |
disabled |
MaybeRefOrGetter<boolean> |
✓ | not shown |
buttons |
MaybeRefOrGetter<number[]> |
✓ | not shown |
restrictInView |
MaybeRefOrGetter<boolean> |
✓ | not shown |
autoScroll |
MaybeRefOrGetter<boolean \| { /** * Speed of auto-scroll. * * @default 2 */ s... |
✓ | not shown |
UseDraggableReturn¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
x |
Ref<number> |
✗ | not shown |
y |
Ref<number> |
✗ | not shown |
position |
Ref<Position> |
✗ | not shown |
isDragging |
ComputedRef<boolean> |
✗ | not shown |
style |
ComputedRef<string> |
✗ | not shown |
Generated by Syntax Scribe