useDraggable

元素拖动

useDraggable 通过跟踪指针事件使任何 HTML 或 SVG 元素可拖拽,返回当前的 xy 位置、表示元素是否正在被拖拽的布尔值以及程序化设置位置的函数。它支持指针、鼠标、触摸和触控笔输入,并可选择将移动限制在容器元素内。

使用场景

  • 构建可拖拽面板、浮动工具栏或可调整大小的小部件
  • 实现仪表板卡片或看板项目的拖拽重新定位功能
  • 创建元素需要自由移动的交互式图表或编辑器

注意事项

  • 触摸支持:在可拖拽元素的 CSS 上设置 touch-action: none 以防止触摸设备上的浏览器滚动干扰。
  • 容器边界:使用 containerElement 选项将拖拽限制在父元素内。hook 将自动计算边界。
  • 回调onStartonMoveonEnd 回调提供精细控制。从 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

参数名描述类型默认值
targetdom对象BasicTarget<HTMLElement | SVGElement> (必填)-
options可选参数UseDraggableOptions | undefined-

UseDraggableOptions

参数名描述类型默认值
exact仅当直接单击元素时才开始拖动booleanfalse
preventDefault阻止默认事件booleanfalse
stopPropagation阻止事件冒泡booleanfalse
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;
}