useDraggable
元素拖动
useDraggable 通过跟踪指针事件使任何 HTML 或 SVG 元素可拖拽,返回当前的 x 和 y 位置、表示元素是否正在被拖拽的布尔值以及程序化设置位置的函数。它支持指针、鼠标、触摸和触控笔输入,并可选择将移动限制在容器元素内。
使用场景
- 构建可拖拽面板、浮动工具栏或可调整大小的小部件
- 实现仪表板卡片或看板项目的拖拽重新定位功能
- 创建元素需要自由移动的交互式图表或编辑器
注意事项
- 触摸支持:在可拖拽元素的 CSS 上设置
touch-action: none以防止触摸设备上的浏览器滚动干扰。 - 容器边界:使用
containerElement选项将拖拽限制在父元素内。hook 将自动计算边界。 - 回调:
onStart、onMove和onEnd回调提供精细控制。从onStart返回false可阻止拖拽开始。 - 清理:所有指针事件监听器在卸载时自动移除。
Usage
Fixed Demo
Live Editor
function Demo() { const el = useRef<HTMLDivElement>(null); const [initialValue, setInitialValue] = useState({ x: 200 / 2.2, y: 120 }); useEffect(() => { setInitialValue({ x: window.innerWidth / 2.2, y: 120 }); }, []); const [x, y, isDragging] = useDraggable(el, { initialValue, preventDefault: true, }); return ( <div> <p style={{ textAlign: "center" }}>检查浮动框</p> <div ref={el} style={{ position: "fixed", cursor: "move", zIndex: 10, touchAction: "none", padding: 10, border: "solid 1px", left: x, top: y, }} > {isDragging ? "拖拽中!" : "👋 拖拽我!"} <div> 我在 {Math.round(x)}, {Math.round(y)} </div> </div> </div> ); };
Result
Relative Demo
Live Editor
function Demo() { const el = useRef<HTMLDivElement>(null); const scope = useRef<HTMLDivElement>(null); const initialValue = { x: 200 / 2.2, y: 120 }; const [x, y, isDragging, setPosition] = useDraggable(el, { initialValue, preventDefault: true, containerElement: scope, }); return ( <div ref={scope} style={{ width: 500, height: 500, border: "1px solid blue", position: "relative", }} > <button style={{ textAlign: "center" }} onClick={() => { setPosition({ x: 250, y: 250, }); }} > 设置位置 </button> <div ref={el} style={{ position: "absolute", cursor: "move", zIndex: 10, touchAction: "none", padding: 10, border: "solid 1px", left: x, top: y, whiteSpace: "nowrap", }} > {isDragging ? "拖拽中!" : "👋 拖拽我!"} <div style={{ whiteSpace: "nowrap" }}> 我在 {Math.round(x)}, {Math.round(y)} </div> </div> </div> ); }
Result
API
useDraggable
Returns
readonly [number, number, boolean, React.Dispatch<React.SetStateAction<Position>>]: 包含以下元素的元组:
- x
- y
- 元素是否在拖动中
- 设置元素的位置
Arguments
| 参数名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| target | dom对象 | BasicTarget<HTMLElement | SVGElement> (必填) | - |
| options | 可选参数 | UseDraggableOptions | undefined | - |
UseDraggableOptions
| 参数名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| exact | 仅当直接单击元素时才开始拖动 | boolean | false |
| preventDefault | 阻止默认事件 | boolean | false |
| stopPropagation | 阻止事件冒泡 | boolean | false |
| draggingElement | 将“pointermove”和“pointerup”事件附加到的dom元素 | BasicTarget<HTMLElement | SVGElement> | window |
| containerElement | 设置拖拽容器边界 | BasicTarget<HTMLElement | SVGAElement> | undefined |
| handle | 触发拖动事件的dom元素 | RefObject<HTMLElement | SVGElement> | target |
| pointerTypes | 监听的事件类型 | PointerType[] | ['mouse', 'touch', 'pen'] |
| initialValue | 初始的元素位置 | Position | { x: 0, y: 0 } |
| onStart | 拖动开始时的回调。 返回“false”以防止拖动 | (position: Position, event: PointerEvent) => void | false | - |
| onMove | 拖动时候的回调 | (position: Position, event: PointerEvent) => void | - |
| onEnd | 拖动结束的回调 | (position: Position, event: PointerEvent) => void | - |
BasicTarget
export type BasicTarget<T extends TargetType = Element> = (() => TargetValue<T>) | TargetValue<T> | MutableRefObject<TargetValue<T>>;
TargetValue
type TargetValue<T> = T | undefined | null;
TargetType
type TargetType = HTMLElement | Element | Window | Document | EventTarget;
PointerType
export type PointerType = 'mouse' | 'touch' | 'pen';
Position
export interface Position {
x: number;
y: number;
}