使用场景
用于设置元素可被拖动改变位置,支持限制拖拽范围,支持自定义触发拖动的元素。
代码演示
如何引入
<script setup lang="ts">
import { DragMove } from '@fibrous/semi-ui-vue';
</script>
基本用法
默认插槽中的元素将能够通过拖拽改变位置。
注意
- DragMove 默认会将可拖拽的元素设置为 absolute 定位;需要保留元素原有布局位置(例如居中的 Modal)时,可设置
positionStrategy="relative"
- DragMove 把监听绑定在默认插槽的每个顶层元素上,每个顶层元素各自可拖动。顶层是组件时绑定在组件的根元素上,因此组件需为单根组件,无需透传 attrs
Drag me
代码
<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>限制拖动范围
传入 constrainer,可以是 'parent',也可以是返回限制可拖拽范围的元素的函数。
注意:constrainer 设置的元素需要为 relative 定位
Constrainer
Drag me
代码
<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>自定义触发拖动的元素
可通过 handler 自定义触发拖动的元素。如果不设置, 则点击任意位置均可拖动;如果设置,则仅点击 handler 部分可拖动。
Constrainer
代码
<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>自定义拖动后的位置处理
可通过 customMove 自定义拖动后的位置处理,该参数设置后,DragMove 组件内部将仅通过参数返回计算后的位置,不做设置,用户按需自行设置新位置。
Constrainer
代码
<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
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| handler | 返回触发拖动的元素,返回 null 时回退为被拖动元素本身。 | Type(() => HTMLElement | null) | undefined |
| constrainer | 限制拖动范围的元素:'parent' 或返回该元素的函数,该元素需要为 relative 定位。 | Type"parent" | (() => HTMLElement | null) | undefined |
| allowInputDrag | 在原生 input、textarea 上按下时是否允许拖动。 | boolean | semiDefault("DragMove", "allowInputDrag", false) |
| allowMove | 按下时判断是否允许拖动的函数,返回 false 则不拖动。 | Type((e: PointerEvent, element: HTMLElement) => boolean) | undefined |
| customMove | 自定义拖动后的位置处理;设置后组件只回传计算出的位置,不再改写元素的 top 与 left。 | Type((element: HTMLElement, top: number, left: number) => void) | undefined |
| positionStrategy | 定位策略,relative 保留元素原有的布局位置。 | "absolute" | "relative" | semiDefault("DragMove", "positionStrategy", "absolute") |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| mouseDown | 鼠标或触控笔按下时触发。 | [e: PointerEvent] |
| mouseMove | 鼠标或触控笔移动时触发。 | [e: PointerEvent] |
| mouseUp | 鼠标或触控笔抬起时触发。 | [e: PointerEvent] |
| touchStart | 触摸开始时触发。 | [e: PointerEvent] |
| touchMove | 触摸移动时触发。 | [e: PointerEvent] |
| touchEnd | 触摸结束时触发。 | [e: PointerEvent] |
| touchCancel | 触摸取消时触发。 | [e: PointerEvent] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| default | 被拖动的内容,其中每个顶层元素或单根组件各自可拖动。 | any |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
DragMove 没有自己的根元素,class、style 等未声明的 attrs 无处可落,会被丢弃;需要的属性请直接写在被拖动的元素上。