使用场景
用于设置元素可被拖动改变位置,支持限制拖拽范围,支持自定义触发拖动的元素。
代码演示
如何引入
<script lang="ts">
import { dragMove } from '@fibrae/semi-ui-svelte';
</script>
基本用法
Svelte 中的 DragMove 是 dragMove attachment。在元素上写 {@attach dragMove(options)},该元素即可通过拖拽改变位置。
注意
- DragMove 默认会将可拖拽的元素设置为 absolute 定位;需要保留元素原有布局位置(例如居中的 Modal)时,可设置
positionStrategy="relative"
dragMove是 attachment 而不是组件:它让所写的那个元素可拖动,每个元素需要各自写一个。它会内联写入position、top、left、cursor和touch-action,所以元素上请用style:指令,不要用会随状态重写的style属性,否则会覆盖这些值。选项在 attachment 运行时读取;传入新的选项对象会让它重新运行,并结束正在进行的拖动
Drag me
代码
<script lang="ts">
import { dragMove } from '@fibrae/semi-ui-svelte';
</script>
<!-- dragMove positions the element absolutely, so the wrapper keeps room for it in the preview -->
<div style="height: 120px">
<div {@attach dragMove()} style:align-items="center" style:background-color="var(--semi-color-primary)" style:border-radius="10px" style:color="var(--semi-white)" style:display="flex" style:font-weight="500" style:height="80px" style:justify-content="center" style:position="absolute" style:width="80px">
Drag me
</div>
</div>限制拖动范围
传入 constrainer,可以是 'parent',也可以是返回限制可拖拽范围的元素的函数。
注意:constrainer 设置的元素需要为 relative 定位
Constrainer
Drag me
代码
<script lang="ts">
import { dragMove } from '@fibrae/semi-ui-svelte';
let container = $state<HTMLDivElement>();
</script>
<div bind:this={container} style="background-color: var(--semi-grey-2); width: 300px; height: 300px; padding: 5px; position: relative; color: var(--semi-white); font-weight: 500">
<span style="color: var(--semi-color-text-0)">Constrainer</span>
<div
{@attach dragMove({ constrainer: () => container ?? null })}
style:align-items="center"
style:background-color="var(--semi-color-primary)"
style:border-radius="10px"
style:display="flex"
style:height="80px"
style:justify-content="center"
style:left="80px"
style:position="absolute"
style:top="80px"
style:width="80px"
>
Drag me
</div>
</div>自定义触发拖动的元素
可通过 handler 自定义触发拖动的元素。如果不设置, 则点击任意位置均可拖动;如果设置,则仅点击 handler 部分可拖动。
Constrainer
代码
<script lang="ts">
import { IconTransparentStroked } from '@fibrae/semi-icons-svelte';
import { dragMove } from '@fibrae/semi-ui-svelte';
let container = $state<HTMLDivElement>();
let handlerEl = $state<HTMLDivElement>();
</script>
<div bind:this={container} style="background-color: var(--semi-grey-2); width: 300px; height: 300px; padding: 5px; position: relative; color: var(--semi-white); font-weight: 500">
<span style="color: var(--semi-color-text-0)">Constrainer</span>
<div
{@attach dragMove({ constrainer: () => container ?? null, handler: () => handlerEl ?? null })}
style:align-items="center"
style:background-color="var(--semi-color-primary)"
style:border-radius="10px"
style:display="flex"
style:height="80px"
style:justify-content="center"
style:left="50px"
style:position="absolute"
style:top="50px"
style:width="80px"
>
<div bind:this={handlerEl} style="width: fit-content; height: fit-content">
<IconTransparentStroked size="large" />
</div>
</div>
</div>自定义拖动后的位置处理
可通过 customMove 自定义拖动后的位置处理,该参数设置后,DragMove 组件内部将仅通过参数返回计算后的位置,不做设置,用户按需自行设置新位置。
Constrainer
代码
<script lang="ts">
import { dragMove } from '@fibrae/semi-ui-svelte';
let container = $state<HTMLDivElement>();
let startPoint: { x: number; y: number } | null = null;
const customMove = (element: HTMLElement, top: number, left: number) => {
if (container && left + 100 > container.offsetWidth) {
element.style.right = `${container.offsetWidth - left - element.offsetWidth}px`;
element.style.left = 'auto';
} else {
element.style.left = left + 'px';
}
element.style.top = top + 'px';
};
const toggleWidth = (element: HTMLElement) => {
element.style.width = element.style.width === '60px' ? '100px' : '60px';
};
const onmousedown = (e: MouseEvent) => {
startPoint = { x: e.clientX, y: e.clientY };
};
const 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;
};
const onkeydown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
toggleWidth(e.currentTarget as HTMLElement);
}
};
</script>
<span>蓝色色块点击可改变宽度,改变前后蓝色色块均不会超出范围限制 </span>
<br />
<br />
<div bind:this={container} style="background-color: var(--semi-grey-2); width: 300px; height: 300px; position: relative; padding: 10px; color: var(--semi-white); font-weight: 500">
<span style="color: var(--semi-color-text-0)">Constrainer</span>
<button
{@attach dragMove({ constrainer: () => container ?? null, customMove })}
style:align-items="center"
style:background-color="var(--semi-color-primary)"
style:border="none"
style:border-radius="10px"
style:box-sizing="content-box"
style:color="inherit"
style:display="flex"
style:font="inherit"
style:height="50px"
style:justify-content="center"
style:left="50px"
style:padding="5px"
style:position="absolute"
style:top="50px"
style:width="60px"
type="button"
{onkeydown}
{onmousedown}
{onmouseup}
>
Drag me
</button>
</div>API
dragMove(options?: DragMoveOptions) 返回 Attachment<HTMLElement>。DragMoveOptions 从 @fibrae/semi-ui-svelte 导出。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| allowInputDrag | 在原生 input、textarea 上按下时是否允许拖动。 | boolean | false |
| allowMove | 按下时判断是否允许拖动的函数,返回 false 则不拖动。 | (e: PointerEvent, element: HTMLElement) => boolean | - |
| constrainer | 限制拖动范围的元素:'parent' 或返回该元素的函数,该元素需要为 relative 定位。 | 'parent' | (() => HTMLElement | null) | - |
| customMove | 自定义拖动后的位置处理;设置后只回传计算出的位置,不再改写元素的 top 与 left。 | (element: HTMLElement, top: number, left: number) => void | - |
| handler | 触发拖动的元素或返回该元素的函数,缺省为被拖动元素本身。 | HTMLElement | (() => HTMLElement | null) | - |
| onMouseDown | 鼠标或触控笔按下时调用。 | (e: PointerEvent) => void | - |
| onMouseMove | 鼠标或触控笔移动时调用。 | (e: PointerEvent) => void | - |
| onMouseUp | 鼠标或触控笔抬起时调用。 | (e: PointerEvent) => void | - |
| onTouchCancel | 指针被系统取消(pointercancel)时调用。 | (e: PointerEvent) => void | - |
| onTouchEnd | 触摸结束时调用。 | (e: PointerEvent) => void | - |
| onTouchMove | 触摸移动时调用。 | (e: PointerEvent) => void | - |
| onTouchStart | 触摸开始时调用。 | (e: PointerEvent) => void | - |
| positionStrategy | 定位策略,relative 保留元素原有的布局位置。 | 'absolute' | 'relative' | 'absolute' |