Plus · DragMove

DragMove

Set elements to change their position by dragging

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
Drag me
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

Constrainer
Drag me
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.

Constrainer
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.

蓝色色块点击可改变宽度,改变前后蓝色色块均不会超出范围限制

Constrainer
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

PropertyDescriptionTypeDefault
handlerReturns the element that starts a drag. Falls back to the dragged element itself when it returns null.Type(() => HTMLElement | null)undefined
constrainerElement that bounds the movement: 'parent' or a function returning it. The element needs relative positioning.Type"parent" | (() => HTMLElement | null)undefined
allowInputDragWhether pressing on a native input or textarea starts a drag.booleansemiDefault("DragMove", "allowInputDrag", false)
allowMoveCalled when a pointer goes down. Return false to not drag.Type((e: PointerEvent, element: HTMLElement) => boolean)undefined
customMoveTakes 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
positionStrategyPositioning strategy. relative keeps the element's place in the layout."absolute" | "relative"semiDefault("DragMove", "positionStrategy", "absolute")

Events

EventDescriptionCallback parameters
mouseDownEmitted when a mouse or pen pointer is pressed.[e: PointerEvent]
mouseMoveEmitted when a mouse or pen pointer moves.[e: PointerEvent]
mouseUpEmitted when a mouse or pen pointer is released.[e: PointerEvent]
touchStartEmitted when a touch pointer is pressed.[e: PointerEvent]
touchMoveEmitted when a touch pointer moves.[e: PointerEvent]
touchEndEmitted when a touch pointer is released.[e: PointerEvent]
touchCancelEmitted when a touch pointer is cancelled.[e: PointerEvent]

Slots

SlotDescriptionScope parameters
defaultContent to drag. Each top-level element, or single-root component, is dragged on its own.any

Exposes

NameDescriptionType
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.