When to use
It is used to set the element that can be dragged to change its position. It supports limiting the drag range and customizing the elements that trigger dragging.
Demos
How to introduce
<script setup lang="ts">
import { DragMove } from '@fibrous/semi-ui-vue';
</script>
Basic usage
Elements in the default slot will be able to change their position by dragging.
Notice
- DragMove uses absolute positioning by default. Set
positionStrategy="relative"when the element should keep its original layout position, such as a centered Modal
- DragMove binds its listeners to each top-level element of the default slot, and each one is dragged on its own. For a component at the top level it binds to the component's root element, so the component must have a single root; it does not need to pass attrs through
Code
<script setup lang="ts">
import { DragMove } from '@fibrous/semi-ui-vue';
</script>
<template>
<!-- DragMove positions the element absolutely, so the wrapper keeps room for it in the preview -->
<div :style="{ height: '120px' }">
<DragMove>
<div
:style="{
backgroundColor: 'var(--semi-color-primary)',
width: '80px',
height: '80px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '10px',
fontWeight: 500,
position: 'absolute',
color: 'var(--semi-white)',
}"
>
Drag me
</div>
</DragMove>
</div>
</template>Limit drag range
Pass in constrainer, either 'parent' or a function that returns the element limiting the draggable range.
Note: The elements returned by the constrainer need to be positioned relative
Code
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { DragMove } from '@fibrous/semi-ui-vue';
const container = useTemplateRef<HTMLDivElement>('container');
</script>
<template>
<div
ref="container"
:style="{
backgroundColor: 'var(--semi-grey-2)',
width: '300px',
height: '300px',
padding: '5px',
position: 'relative',
color: 'var(--semi-white)',
fontWeight: 500,
}"
>
<span :style="{ color: 'var(--semi-color-text-0)' }">Constrainer</span>
<DragMove :constrainer="() => container">
<div
:style="{
backgroundColor: 'var(--semi-color-primary)',
width: '80px',
height: '80px',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: '80px',
left: '80px',
}"
>
Drag me
</div>
</DragMove>
</div>
</template>Customize elements that trigger dragging
Passing in handler, this function returns the element that triggered the drag. If not set, you can click anywhere to drag; if set, only the part of the element returned by the handler can be dragged.
Code
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { IconTransparentStroked } from '@fibrous/semi-icons-vue';
import { DragMove } from '@fibrous/semi-ui-vue';
const handlerEl = useTemplateRef<HTMLDivElement>('handlerEl');
const container = useTemplateRef<HTMLDivElement>('container');
</script>
<template>
<div
ref="container"
:style="{
backgroundColor: 'var(--semi-grey-2)',
width: '300px',
height: '300px',
padding: '5px',
position: 'relative',
color: 'var(--semi-white)',
fontWeight: 500,
}"
>
<span :style="{ color: 'var(--semi-color-text-0)' }">Constrainer</span>
<DragMove :constrainer="() => container" :handler="() => handlerEl">
<div
:style="{
backgroundColor: 'var(--semi-color-primary)',
width: '80px',
height: '80px',
borderRadius: '10px',
position: 'absolute',
top: '50px',
left: '50px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}"
>
<div ref="handlerEl" :style="{ width: 'fit-content', height: 'fit-content' }">
<IconTransparentStroked size="large" />
</div>
</div>
</DragMove>
</div>
</template>Customize position processing after dragging
You can customize the position processing after dragging through customMove. After this parameter is set, the DragMove component will only return the calculated position through the parameters without setting it. The user can set the new position as needed.
Code
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { DragMove } from '@fibrous/semi-ui-vue';
const container = useTemplateRef<HTMLDivElement>('container');
let startPoint: { x: number; y: number } | null = null;
function customMove(element: HTMLElement, top: number, left: number) {
const bounds = container.value;
if (bounds && left + 100 > bounds.offsetWidth) {
element.style.right = `${bounds.offsetWidth - left - element.offsetWidth}px`;
element.style.left = 'auto';
} else {
element.style.left = left + 'px';
}
element.style.top = top + 'px';
}
function toggleWidth(element: HTMLElement) {
element.style.width = element.style.width === '60px' ? '100px' : '60px';
}
function onMouseDown(e: MouseEvent) {
startPoint = { x: e.clientX, y: e.clientY };
}
function onMouseUp(e: MouseEvent) {
if (startPoint && Math.abs(e.clientX - startPoint.x) < 5 && Math.abs(e.clientY - startPoint.y) < 5) {
toggleWidth(e.currentTarget as HTMLElement);
}
startPoint = null;
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'Enter') {
toggleWidth(e.currentTarget as HTMLElement);
}
}
</script>
<template>
<span>蓝色色块点击可改变宽度,改变前后蓝色色块均不会超出范围限制 </span>
<br />
<br />
<div
ref="container"
:style="{
backgroundColor: 'var(--semi-grey-2)',
width: '300px',
height: '300px',
position: 'relative',
padding: '10px',
color: 'var(--semi-white)',
fontWeight: 500,
}"
>
<span :style="{ color: 'var(--semi-color-text-0)' }">Constrainer</span>
<DragMove :constrainer="() => container" :customMove="customMove">
<button
:style="{
backgroundColor: 'var(--semi-color-primary)',
boxSizing: 'content-box',
width: '60px',
height: '50px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: '50px',
left: '50px',
border: 'none',
borderRadius: '10px',
padding: '5px',
color: 'inherit',
font: 'inherit',
}"
type="button"
@keydown="onKeyDown"
@mousedown="onMouseDown"
@mouseup="onMouseUp"
>
Drag me
</button>
</DragMove>
</div>
</template>API
Props
| Property | Description | Type | Default |
|---|---|---|---|
| handler | Returns the element that starts a drag. Falls back to the dragged element itself when it returns null. | Type(() => HTMLElement | null) | undefined |
| constrainer | Element that bounds the movement: 'parent' or a function returning it. The element needs relative positioning. | Type"parent" | (() => HTMLElement | null) | undefined |
| allowInputDrag | Whether pressing on a native input or textarea starts a drag. | boolean | semiDefault("DragMove", "allowInputDrag", false) |
| allowMove | Called when a pointer goes down. Return false to not drag. | Type((e: PointerEvent, element: HTMLElement) => boolean) | undefined |
| customMove | Takes the new position instead of the element's top and left; the component only reports the computed position. | Type((element: HTMLElement, top: number, left: number) => void) | undefined |
| positionStrategy | Positioning strategy. relative keeps the element's place in the layout. | "absolute" | "relative" | semiDefault("DragMove", "positionStrategy", "absolute") |
Events
| Event | Description | Callback parameters |
|---|---|---|
| mouseDown | Emitted when a mouse or pen pointer is pressed. | [e: PointerEvent] |
| mouseMove | Emitted when a mouse or pen pointer moves. | [e: PointerEvent] |
| mouseUp | Emitted when a mouse or pen pointer is released. | [e: PointerEvent] |
| touchStart | Emitted when a touch pointer is pressed. | [e: PointerEvent] |
| touchMove | Emitted when a touch pointer moves. | [e: PointerEvent] |
| touchEnd | Emitted when a touch pointer is released. | [e: PointerEvent] |
| touchCancel | Emitted when a touch pointer is cancelled. | [e: PointerEvent] |
Slots
| Slot | Description | Scope parameters |
|---|---|---|
| default | Content to drag. Each top-level element, or single-root component, is dragged on its own. | any |
Exposes
| Name | Description | Type |
|---|---|---|
| None |
DragMove has no root element of its own, so undeclared attrs such as class and style have nowhere to land and are dropped. Put what you need directly on the dragged element.