useDragControls
Controls that start, stop and cancel a motion component's drag gesture from other elements.
A drag usually starts when the pointer presses down on a motion component with the drag prop and then moves.
Sometimes the drag should start somewhere else, like from a handle on a card, or from any point along a video scrubber's track. useDragControls creates controls that start a drag gesture from any pointer event.
<script lang="ts">
import { motion, useDragControls } from "motion-sv";
const controls = useDragControls();
let area = $state<HTMLElement>();
</script>
<div class="area" bind:this={area}>
<motion.div class="card" drag dragControls={controls} dragListener={false} dragConstraints={area}>
<div class="handle" role="presentation" onpointerdown={(event) => controls.start(event)}></div>
<p>Drag me by the handle</p>
</motion.div>
</div>
<style>
.area {
display: grid;
place-items: center;
width: min(560px, 100%);
height: 220px;
border-radius: 16px;
background: rgb(255 0 136 / 0.08);
}
.area :global(.card) {
width: 220px;
border-radius: 12px;
background: #ff0088;
color: white;
overflow: hidden;
}
.handle {
height: 28px;
background: rgb(0 0 0 / 0.2);
cursor: grab;
touch-action: none;
}
p {
margin: 0;
padding: 16px;
}
</style> Usage
Import useDragControls from motion-sv:
import { useDragControls } from "motion-sv"; useDragControls returns drag controls. Pass them to a draggable motion component with the dragControls prop:
<script lang="ts">
import { motion, useDragControls } from "motion-sv";
const controls = useDragControls();
</script>
<motion.div drag dragControls={controls} /> Then start a drag from another element's pointerdown event with controls.start:
<div onpointerdown={(event) => controls.start(event)}></div> The same controls can be passed to more than one motion component. start then drags all of them.
Touch support
Give the element that starts the drag touch-action: none. Otherwise, touch devices treat the gesture as a scroll and cancel the drag.
<div style="touch-action: none" onpointerdown={(event) => controls.start(event)}></div> Snap to cursor
By default, the dragged element moves by as much as the pointer does, from wherever it is.
Pass snapToCursor: true to start to center the element on the pointer first:
controls.start(event, { snapToCursor: true }); <script lang="ts">
import { motion, useDragControls } from "motion-sv";
const controls = useDragControls();
let track = $state<HTMLElement>();
</script>
<div class="track" role="presentation" bind:this={track} onpointerdown={(event) => controls.start(event, { snapToCursor: true })}>
<motion.div
class="thumb"
drag="x"
dragControls={controls}
dragListener={false}
dragConstraints={track}
dragElastic={0}
dragMomentum={false}
/>
</div>
<style>
.track {
display: flex;
align-items: center;
width: min(400px, 100%);
height: 40px;
border-radius: 20px;
background: rgb(153 17 255 / 0.15);
cursor: pointer;
touch-action: none;
}
.track :global(.thumb) {
width: 40px;
height: 40px;
border-radius: 50%;
background: #9911ff;
}
</style> Disable automatic drag
With drag controls, the motion component still starts a drag gesture when it receives a pointerdown event itself. To only drag through the controls, as with a drag handle, set dragListener to false:
<motion.div drag dragControls={controls} dragListener={false}>
<div class="handle" onpointerdown={(event) => controls.start(event)}></div>
<!-- Pressing anywhere else won't start a drag -->
</motion.div> Methods
start(event, options?)
Starts a drag gesture on every motion component the controls are passed to, from a pointerdown event.
Options:
snapToCursor: Center the element on the pointer when the drag starts. Default:false.distanceThreshold: How far, in pixels, the pointer must move before the drag starts. Default:3.
stop()
Ends the drag gesture as if the pointer had been released. The momentum animation still runs, along with any dragConstraints and dragSnapToOrigin animations, and onDragEnd fires.
cancel()
Ends the drag gesture straight away. The element stays where it is, and onDragEnd doesn't fire.
<svelte:window onkeydown={(event) => event.key === "Escape" && controls.cancel()} />