LogoLogo
首页示例Github
  • 概述
  • 介绍
    • 安装指南
    • 快速开始
  • API 文档
    • DndContext
      • 碰撞检测算法
      • useDndContext
      • useDndMonitor
    • Droppable
      • useDroppable
    • Draggable
      • useDraggable
      • 拖动浮层
    • Sensors
      • Pointer
      • Mouse
      • Touch
      • Keyboard
    • Modifiers
  • 预置能力
    • Sortable
      • Sortable Context
      • useSortable
  • 指南
    • 无障碍指南
  • 说明
由 GitBook 提供支持
在本页
  • 什么时候应该使用一个拖动浮层呢?
  • 使用方式
  • 模式
  • 展示型组件
  • Portals
  • Props
  • Children
  • className 与内联样式 style
  • 放置动画
  • Modifiers
  • 过渡效果
  • 包装节点
  • z-index

这有帮助吗?

在GitHub上编辑
  1. API 文档
  2. Draggable

拖动浮层

上一页useDraggable下一页Sensors

最后更新于1年前

这有帮助吗?

<DragOverlay> 组件能够为 draggable 元素渲染一个拖动浮层,并且该浮层是脱离普通文档流、相对于当前网页的可视区域进行定位的。

什么时候应该使用一个拖动浮层呢?

  • 需要预览当前的 draggable 源节点即将放置的位置,可以在拖动时更新 draggable 源节点的位置,并且不会影响拖动浮层。

  • 需要将元素从一个容器拖到另一个容器中,此时强烈推荐使用 <DragOverlay> 组件,draggable 元素在拖动时可以从源容器中卸载并在另一个容器中执行挂载,并且也不会影响拖动浮层。

  • draggable 元素位于一个**可滚动的容器中,**也建议使用 <DragOverlay> 组件,否则你需要自行将 draggable 元素设置为 position: fixed,才能使得该元素不会在滚动容器中溢出,并且滚动时在容器中的滚动位置也能够正常。

  • draggable 元素位于一个虚拟列表中,此时绝对应该需要一个拖动浮层,因为随着虚拟列表容器的滚动,在拖动时 draggable 源节点会被卸载。

  • 无需自行处理就能在放置时有一个丝滑的动画。

使用方式

在 <DragOverlay> 组件的 children 支持渲染任何合法的 JSX。

<DragOverlay> 组件需要保持始终处于挂载状态,才会执行放置动画。如果条件式地渲染 <DragOverlay> 组件,它的放置动画是无法正常工作的。

反而是应该需要条件式地渲染传递给 <DragOverlay> 组件的 children。

import React, { useState } from "react";
import { DndContext, DragOverlay } from "@dnd-kit/core";

import { Draggable } from "./Draggable";

/* <Item> 和 <ScrollableList> 组件的实现细节与以下示例无关,因此略过 */

function App() {
  const [items] = useState(["1", "2", "3", "4", "5"]);
  const [activeId, setActiveId] = useState(null);

  return (
    <DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
      <ScrollableList>
        {items.map((id) => (
          <Draggable key={id} id={id}>
            <Item value={`Item ${id}`} />
          </Draggable>
        ))}
      </ScrollableList>

      <DragOverlay>
        {activeId ? <Item value={`Item ${activeId}`} /> : null}
      </DragOverlay>
    </DndContext>
  );

  function handleDragStart(event) {
    setActiveId(event.active.id);
  }

  function handleDragEnd() {
    setActiveId(null);
  }
}
import React from "react";
import { useDraggable } from "@dnd-kit/core";

function Draggable(props) {
  const { attributes, listeners, setNodeRef } = useDraggable({
    id: props.id,
  });

  return (
    <li ref={setNodeRef} {...listeners} {...attributes}>
      {props.children}
    </li>
  );
}

模式

展示型组件

使用该模式,同一组件就具有两种使用版本,一种是拖动浮层中渲染的展示型组件,另一种是内部渲染了展示型组件的 draggable 元素。

包装节点

从上面的例子中可以看出,可以将一个包装节点与任意子节点一起渲染为 draggable 元素,从而创建一个小型的抽象组件:

import React from "react";
import { useDraggable } from "@dnd-kit/core";

function Draggable(props) {
  const Element = props.element || "div";
  const { attributes, listeners, setNodeRef } = useDraggable({
    id: props.id,
  });

  return (
    <Element ref={setNodeRef} {...listeners} {...attributes}>
      {props.children}
    </Element>
  );
}

使用该模式,可以在 <Draggable> 与 <DragOverlay> 组件中渲染展示型组件。

import React, { useState } from "react";
import { DndContext, DragOverlay } from "@dnd-kit/core";

import { Draggable } from "./Draggable";

/* <Item> 组件的实现细节与以下示例无关,因此略过 */

function App() {
  const [isDragging, setIsDragging] = useState(false);

  return (
    <DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
      <Draggable id="my-draggable-element">
        <Item />
      </Draggable>

      <DragOverlay>{isDragging ? <Item /> : null}</DragOverlay>
    </DndContext>
  );

  function handleDragStart() {
    setIsDragging(true);
  }

  function handleDragEnd() {
    setIsDragging(false);
  }
}

Ref 转发

import React, { forwardRef } from "react";

const Item = forwardRef(({ children, ...props }, ref) => {
  return (
    <li {...props} ref={ref}>
      {children}
    </li>
  );
});

这样,同一组件就具有了两种使用版本,一种是纯展示型态,另一种是渲染了展示型态组件的 draggable 元素,并且不需要添加额外的包装节点。

import React from 'react';
import {useDraggable} from '@dnd-kit/core';

function DraggableItem(props) {
  const {attributes, listeners, setNodeRef} = useDraggable({
    id: props.id,
  });

  return (
    <Item ref={setNodeRef} {...attributes} {...listeners}>
      {value}
    </Item>
  )
});

Portals

默认情况下,拖动浮层不是渲染在 portal 中,而是渲染在当前 draggable 元素的渲染容器中。

import React, { useState } from "react";
import { createPortal } from "react-dom";
import { DndContext, DragOverlay } from "@dnd-kit/core";

function App() {
  return (
    <DndContext>
      {createPortal(<DragOverlay>{/* ... */}</DragOverlay>, document.body)}
    </DndContext>
  );
}

Props

{
  adjustScale?: boolean;
  children?: React.ReactNode;
  className?: string;
  dropAnimation?: DropAnimation | null;
  style?: React.CSSProperties;
  transition?: string | TransitionGetter;
  modifiers?: Modifiers;
  wrapperElement?: keyof JSX.IntrinsicElements;
  zIndex?: number;
}

Children

在 <DragOverlay> 组件的 children 支持渲染任何合法的 JSX。但是需要注意在其中不能再使用 useDraggable。

只能条件式地渲染 <DragOverlay> 组件的 children,而不能条件式地渲染 <DragOverlay> 组件本身,否则它的放置动画是无法正常工作的。

className 与内联样式 style

<DragOverlay
  className="my-drag-overlay"
  style={{
    width: 500,
  }}
>
  {/* ... */}
</DragOverlay>

放置动画

使用 dropAnimation 参数可以配置放置时的动画效果。

interface DropAnimation {
  duration: number;
  easing: string;
}
<DragOverlay
  dropAnimation={{
    duration: 500,
    easing: "cubic-bezier(0.18, 0.67, 0.6, 1.22)",
  }}
>
  {/* ... */}
</DragOverlay>

将 dropAnimation 设置为 null,可以禁用放置动画。

<DragOverlay dropAnimation={null}>{/* ... */}</DragOverlay>

<DragOverlay> 组件需要保持始终处于挂载状态,才会执行放置动画。如果条件式地渲染 <DragOverlay> 组件,它的放置动画是无法正常工作的。

Modifiers

比如,利用 modifiers 可以将 <DragOverlay> 的移动限制在窗口边界内:

import { DndContext, DragOverlay } from "@dnd-kit";
import { restrictToWindowEdges } from "@dnd-kit/modifiers";

function App() {
  return (
    <DndContext>
      {/* ... */}
      <DragOverlay modifiers={[restrictToWindowEdges]}>{/* ... */}</DragOverlay>
    </DndContext>
  );
}

过渡效果

function defaultTransition(activatorEvent) {
  const isKeyboardActivator = activatorEvent instanceof KeyboardEvent;

  return isKeyboardActivator ? "transform 250ms ease" : undefined;
}

包装节点

默认情况下,<DragOverlay> 组件会在子元素上包装一层 div 节点。若 draggable 节点是列表元素,因为在非 ul 节点下使用 li 是不合法的 HTML,可能需要使用 ul 替代默认的 div 节点:

<DragOverlay wrapperElement="ul">{/* ... */}</DragOverlay>

z-index

在某些的使用场景下,相较于直接转变 对应的 draggable 源节点的位置,你可能更应该使用一个拖动浮层,比如:

经验表明,<DragOverlay> 组件应当尽量渲染在 draggable 组件之外,并遵循以保持良好的关注点分离。

这是一种可选的模式,但仍然建议将 draggable 元素设计为,从而与 @dnd-kit 解耦。

使用 可以将展示型组件与 useDraggable 进行关联:

如果要将 <DragOverlay> 渲染在其他容器中,可以从 react-dom 中引入 方法进行实现。

若要对 DragOverlay 组件 children 的的渲染样式进行自定义,可以使用 className 与 style 这两个参数:

duration 选项是一个 number 类型,单位为 milliseconds, 其默认值为 250 毫秒(milliseconds)。easing 选项是一个 string 类型,表示一个合法的 ,其默认值为 ease.

利用 Modifiers 可以动态地修改 sensors 检测到的移动坐标,它们的使用场景非常广泛,阅读 可以了解更多。

默认情况下,<DragOverlay> 没有任何过渡效果,除非 被激活。使用 transition 参数可以创建一个函数,该函数根据返回过渡效果。默认的实现方式是:

zIndex 参数用于设置拖动浮层的 。 考虑到兼容性,其默认值设置为 999,不过建议可以设置一个较低的值。

useDraggable
展示型组件
ref 转发模式
createPortal
CSS easing 函数
Modifiers
z-order
展示型组件模式
包装节点
Keyboard sensor
activator 事件