使用场景
用于设置元素可被拖动改变位置,支持限制拖拽范围,支持自定义触发拖动的元素。
代码演示
如何引入
import { DragMove } from '@fibrous/semi-ui-solid';
基本用法
被 DragMove 包裹的元素将能够通过拖拽改变位置。
注意
- DragMove 默认会将可拖拽的元素设置为 absolute 定位;需要保留元素原有布局位置(例如居中的 Modal)时,可设置
positionStrategy="relative"
- DragMove 把监听绑定在
children渲染出的每个顶层 DOM 元素上,每个顶层元素各自可拖动。文本节点无法拖动,需要包在元素中;自定义组件渲染出的顶层元素同样会被绑定,无需透传 props 或转发 ref
Drag me
代码
import { DragMove } from '@fibrous/semi-ui-solid';
export default function DragMoveDemo() {
// DragMove positions the element absolutely, so the wrapper keeps room for it in the preview
return (
<div style={{ height: '120px' }}>
<DragMove>
<div
style={{
'background-color': 'var(--semi-color-primary)',
width: '80px',
height: '80px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
'border-radius': '10px',
'font-weight': '500',
position: 'absolute',
color: 'var(--semi-white)',
}}
>
Drag me
</div>
</DragMove>
</div>
);
}限制拖动范围
传入 constrainer,可以是 'parent',也可以是返回限制可拖拽范围的元素的函数。
注意:constrainer 设置的元素需要为 relative 定位
Constrainer
Drag me
代码
import { DragMove } from '@fibrous/semi-ui-solid';
export default function DragMoveDemo() {
let container: HTMLDivElement | undefined;
return (
<div
ref={(element) => (container = element)}
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>
<DragMove constrainer={() => container ?? null}>
<div
style={{
'background-color': 'var(--semi-color-primary)',
width: '80px',
height: '80px',
'border-radius': '10px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
position: 'absolute',
top: '80px',
left: '80px',
}}
>
Drag me
</div>
</DragMove>
</div>
);
}自定义触发拖动的元素
可通过 handler 自定义触发拖动的元素。如果不设置, 则点击任意位置均可拖动;如果设置,则仅点击 handler 部分可拖动。
Constrainer
代码
import { IconTransparentStroked } from '@fibrous/semi-icons-solid';
import { DragMove } from '@fibrous/semi-ui-solid';
export default function DragMoveDemo() {
let handlerElement: HTMLDivElement | undefined;
let container: HTMLDivElement | undefined;
return (
<div
ref={(element) => (container = element)}
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>
<DragMove handler={() => handlerElement ?? null} constrainer={() => container ?? null}>
<div
style={{
'background-color': 'var(--semi-color-primary)',
width: '80px',
height: '80px',
'border-radius': '10px',
position: 'absolute',
top: '50px',
left: '50px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
}}
>
<div ref={(element) => (handlerElement = element)} style={{ width: 'fit-content', height: 'fit-content' }}>
<IconTransparentStroked size="large" />
</div>
</div>
</DragMove>
</div>
);
}自定义拖动后的位置处理
可通过 customMove 自定义拖动后的位置处理,该参数设置后,DragMove 组件内部将仅通过参数返回计算后的位置,不做设置,用户按需自行设置新位置。
Constrainer
代码
import { DragMove } from '@fibrous/semi-ui-solid';
export default function DragMoveDemo() {
let containerRef: HTMLDivElement | undefined;
let elementRef: HTMLButtonElement | undefined;
let startPoint: { x: number; y: number } | null = null;
const customMove = (element: HTMLElement, top: number, left: number) => {
const container = containerRef;
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 onMouseDown = (e: MouseEvent) => {
startPoint = {
x: e.clientX,
y: e.clientY,
};
};
const toggleWidth = () => {
if (!elementRef) return;
elementRef.style.width = elementRef.style.width === '60px' ? '100px' : '60px';
};
const onMouseUp = (e: MouseEvent) => {
if (startPoint) {
const { x, y } = startPoint;
if (Math.abs(e.clientX - x) < 5 && Math.abs(e.clientY - y) < 5) {
toggleWidth();
}
}
startPoint = null;
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
toggleWidth();
}
};
return (
<>
<span>蓝色色块点击可改变宽度,改变前后蓝色色块均不会超出范围限制 </span>
<br />
<br />
<div
ref={(element) => (containerRef = element)}
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>
<DragMove constrainer={() => containerRef ?? null} customMove={customMove}>
<button
type="button"
style={{
'background-color': 'var(--semi-color-primary)',
'box-sizing': 'content-box',
width: '60px',
height: '50px',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
position: 'absolute',
top: '50px',
left: '50px',
border: 'none',
'border-radius': '10px',
padding: '5px',
color: 'inherit',
font: 'inherit',
}}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onKeyDown={onKeyDown}
ref={(element) => (elementRef = element)}
>
Drag me
</button>
</DragMove>
</div>
</>
);
}API
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| allowInputDrag | 在原生 input、textarea 上按下时是否允许拖动。 | boolean | false |
| allowMove | 按下时判断是否允许拖动的函数,返回 false 则不拖动。 | Type(e: PointerEvent, element: HTMLElement) => boolean | - |
| children | 被拖动的内容。每个顶层 DOM 元素各自可拖动,文本节点无法拖动。 | JSX.Element | - |
| constrainer | 限制拖动范围的元素:'parent' 表示父级元素,或返回该元素的函数;该元素需要为 relative 定位。 | Type'parent' | (() => HTMLElement | null) | - |
| customMove | 自定义拖动后的位置处理;设置后组件只在 requestAnimationFrame 中回传计算出的位置,不再改写元素的 top 与 left。 | Type(element: HTMLElement, top: number, left: number) => void | - |
| handler | 返回触发拖动的元素,返回 null 时回退为被拖动元素本身。 | Type() => HTMLElement | null | - |
| onMouseDown | 鼠标或触控笔按下时触发。 | Type(e: PointerEvent) => void | - |
| onMouseMove | 鼠标或触控笔移动时触发。 | Type(e: PointerEvent) => void | - |
| onMouseUp | 鼠标或触控笔抬起时触发。 | Type(e: PointerEvent) => void | - |
| onTouchCancel | 指针被系统取消(pointercancel)时触发。 | Type(e: PointerEvent) => void | - |
| onTouchEnd | 触摸结束时触发。 | Type(e: PointerEvent) => void | - |
| onTouchMove | 触摸移动时触发。 | Type(e: PointerEvent) => void | - |
| onTouchStart | 触摸开始时触发。 | Type(e: PointerEvent) => void | - |
| positionStrategy | 定位策略,relative 保留元素原有的布局位置。 | 'absolute' | 'relative' | "absolute" |
DragMove 没有自己的根元素,class、style、ref 与原生属性等未声明的属性无处可落,会被丢弃;需要的属性请直接写在被拖动的元素上。