Plus 组件

拖拽移动

可通过拖拽改变位置

使用场景

用于设置元素可被拖动改变位置,支持限制拖拽范围,支持自定义触发拖动的元素。

代码演示

如何引入

<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 上按下时是否允许拖动。booleansemiDefault("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 无处可落,会被丢弃;需要的属性请直接写在被拖动的元素上。