指南
EffDND 仅需属性即可为您的 HTML 添加拖放功能。无需 JavaScript 调用,也无需任何设置,只需导入一次即可。本指南将解释您可以使用它做什么以及如何操作。
入门
npm i effdnd只需在您的应用中导入一次:
import 'effdnd'; // 图书馆
import 'effdnd/style.css'; // 可选:默认样式就是这样。任何带有以下属性的元素都会立即变为可拖动状态——即使是之后添加到页面中的元素。
框架: 可与任何框架兼容。无需组件包装器。
工作原理
EffDND 的核心理念只有一个,本指南中的其他内容都只是对其的延伸:
- 只有
trigger才能启动拖拽操作。 任何物体本身都无法拖动。你需要抓住一个标记为data-dnd-trigger的拖拽手柄才能开始拖拽;如果没有触发器,任何物体都不会移动。 - 实际移动的是
item对象。 触发器通常位于item对象内部,因此整个元素会跟随你的指针移动,但它们不必是同一个节点。 scope是沙盒。item对象只能在其scope范围内移动和放置。范围之外的放置区域或容器会被忽略。
这是基本情况:scope 范围内的 item 对象上的 trigger。
<div data-dnd-scope="board"> <!-- 范围:沙盒 -->
<div data-dnd-item="a"> <!-- item: 移动的内容 -->
<span data-dnd-trigger>⠿</span> <!-- 触发器:你抓取的东西 -->
抓住我
</div>
</div>接下来的所有功能——重新排序、传输、滚动或下面的触发参数——都只是对这一核心行为进行改进或扩展。
拖动时的样式。 您看到的移动元素是
item的克隆(副本), 并插入到该元素的自身父元素中。这意味着级联样式和继承样式,以及针对父元素选择器(ul > li、.zone .card等)编写的规则, 仍然适用于移动的克隆元素——无需为每种颜色单独设置。原始元素保持在列表中的原位,并通过passive状态变暗(参见下文的样式)。 注意——已转换的祖先元素。 克隆元素的position:fixed属性使其与布局分离。
但是,如果该项的任何祖先元素具有
transform、filter、perspective(或will-change:transform),浏览器会将fixed视为absolute:克隆项将相对于该祖先元素进行定位,并且可能会出现偏移(并且它会随着可滚动的变换容器一起滚动)。避免拖拽项的祖先元素具有变换属性,或者通过data-dnd-state和您为该项设置的内联样式来覆盖固定视觉样式。
属性概览
| 属性 | 功能 |
|---|---|
data-dnd-trigger | 标记用户拖动的“手柄” |
data-dnd-item | 标记被拖动的元素 |
data-dnd-scope | 将拖放目标限制在一个容器内 |
data-dnd-reorder | 将容器转换为可排序列表(x 或 y) |
data-dnd-target | 标记放置区域 |
data-dnd-transfer | 为放置区域添加操作:append、prepend 或 remove |
data-dnd-scroll | 拖动时自动滚动容器 |
data-dnd-transition | 平滑移动动画 |
data-dnd-disabled | 禁用触发器 |
data-dnd-state | 拖拽过程中设置的运行时状态(active/passive)— 用于样式设置 |
最基本的情况只需要其中三个:容器(scope)内元素(item)的句柄(trigger)。其他所有元素都是可选的,用于添加不同的行为。
触发器参数
由于 trigger 是入口点,因此它是您最常调整的属性。它接受以分号分隔的参数列表:
<span data-dnd-trigger="dist:12;axis:y;scope:board;item:task-1">⠿</span>需要设置拖动距离(dist)
防止点击时意外拖动——只有当指针移动指定像素数后才会进行拖动:
<span data-dnd-trigger="dist:12">⠿</span> <!-- 指针移动 12px 后才会拖动 -->锁定到单轴(axis)
<span data-dnd-trigger="axis:x">⠿</span> <!-- 仅限水平方向 -->
<span data-dnd-trigger="axis:y">⠿</span> <!-- 仅限垂直方向 -->指定一个特定的容器(scope)
默认情况下,EffDND 会通过查看 DOM 来查找最近的 scope。当您需要指定一个特定的 scope 时,请为其命名:
<span data-dnd-trigger="scope:board">⠿</span>指定要移动的上一级项(item)
触发器可以指定要移动的上一级项:
<span data-dnd-trigger="scope:board;item:task-1">⠿</span>限制投放目标(target)
仅允许投放到 data-dnd-target 以给定名称开头的区域:
<span data-dnd-trigger="scope:board;target:drop">⠿</span>在整个页面上自由移动(scope:*)
特殊的 scope:* 值会完全忽略任何 scope 边界:
<span data-dnd-trigger="scope:*">⠿</span>然后可以将该项目拖动到页面上的任何位置。
禁用触发器
<span data-dnd-trigger data-dnd-disabled>⠿</span>移除该属性即可重新启用。
典型场景
以下是实际界面中各个组件的常见组合方式。
重新排序:可排序列表
使用 data-dnd-reorder="y" 将列表项包裹起来,标记每个列表项,并添加一个句柄。
<ul data-dnd-scope="todo" data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Buy milk</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Read a book</li>
</ul>y参数按垂直方向排序,x参数按水平方向排序。- 只有列表的直接子项才会重新排列。
转移:在容器之间移动项目
将容器标记为放置区并为其添加转移操作。
<div data-dnd-scope="sprint">
<div class="zone" data-dnd-target="backlog" data-dnd-transfer="append">Backlog</div>
<div class="zone" data-dnd-target="done" data-dnd-transfer="prepend">Done</div>
</div>append— 该项目被添加到区域的末尾。prepend— 该项目被添加到区域的开始。remove— 该项目被删除(非常适合垃圾箱)。
拖放区域必须位于同一“范围”内(或使用“范围:*”,见上文)。
组合:带有垃圾箱的可排序列表
<div data-dnd-scope="mailbox">
<ul data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Invoice</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Newsletter</li>
</ul>
<button data-dnd-target="trash" data-dnd-transfer="remove">Delete</button>
</div>这里列表会自动排序,并且可以通过拖放按钮删除行。
看板:列和卡片
给看板添加 data-dnd-reorder="x" 参数以对列进行排序,并为每列的卡片区域添加一个
垂直重排序区域和一个转移接收器。
<div data-dnd-scope="kanban" data-dnd-reorder="x"> <!-- 对列进行排序 -->
<div class="col" data-dnd-item="col-1">
<div class="col-head"><span data-dnd-trigger>⠿</span>Backlog</div>
<div class="cards" data-dnd-reorder="y"
data-dnd-target="col-1" data-dnd-transfer="append"> <!-- 分类并接受卡片 -->
<div class="card" data-dnd-item="task-1"><span data-dnd-trigger>⠿</span>Write spec</div>
</div>
</div>
<!-- 更多列 ... -->
</div>拖动列标题可重新排列列顺序;拖动卡片可在列之间移动。
调整常用行为
自动滚动长容器
将 data-dnd-scroll 附加到可滚动元素:
<div data-dnd-scroll="threshold:50;speed:12">threshold(默认值30)— 滚动开始的起始距离,单位为像素。speed(默认值10)— 滚动速度,单位为像素/帧。
现在,在边缘附近拖动会滚动容器,并且越靠近边缘,滚动速度越快。
平滑动画
<div data-dnd-transition="150ms ease">…</div>默认值为 100ms linear。
通过 JavaScript 响应拖拽事件
你仍然可以这样做——EffDND 只是将其设为可选。以下每个函数都会返回一个取消订阅函数:
import { onDrag, onDrop, onReorder, onTransfer, onDragStart, onDragEnd } from 'effdnd';
onReorder((event) => {
console.log('Item was reordered:', event.detail.keys.item);
});可用事件:effdragstart、effdrag、effdragend、effdragenter、effdragleave、effdrop、effreorder、efftransfer。
每个事件都会提供一个可靠的“详情”对象:
event.detail.keys.item; // 哪个元素
event.detail.keys.scope; // 在哪个范围内
event.detail.keys.target; // 哪个目标
event.detail.item;您可能会觉得有用的助手
import { getItem, getScope, getTargets, getReorderContainer, reset } from 'effdnd';
getItem(trigger); // 元素所属的项
getScope(trigger); // 元素所属的作用域
getTargets(trigger); // 可用于触发器的投放区域
reset(item); // 将物品弹回原位使用 data-dnd-state 进行样式设置
在拖拽过程中,EffDND 会为相关元素添加运行时 data-dnd-state 属性。这样,您就可以轻松地为当前拖拽的元素设置样式,无需编写内联 JavaScript。当没有元素被拖拽时,data-dnd-state 属性不会被应用。
状态值
| 元素 | 状态 | 含义 |
|---|---|---|
data-dnd-item | active | 移动的克隆元素 — 当前跟随鼠标指针的元素 |
data-dnd-item | passive | 原始元素保持原位,在克隆元素后方变暗 |
data-dnd-scope | active | 鼠标指针位于作用域边界之外 |
data-dnd-scope | passive | 鼠标指针位于作用域内 — 拖动时正常显示 |
data-dnd-target | active | 当前鼠标悬停的放置区域 |
data-dnd-target | passive | 可放置的有效放置区域 |
捆绑的 index.css 文件已经基于这些选择器构建了美观的默认样式,您可以使用相同的选择器来覆盖它们:
/* 默认值来自 index.css */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.2; }
[data-dnd-item][data-dnd-state="active"] { opacity: 0.75; z-index: 1000; }
/* 您自己的主题 */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.08; }
[data-dnd-item][data-dnd-state="active"] {
opacity: 1;
box-shadow: 0 0 0 2px cornflowerblue;
border-radius: 8px;
}
[data-dnd-scope][data-dnd-state="active"] { outline: 2px dashed tomato; }
[data-dnd-target][data-dnd-state="active"] { background: rgba(100, 200, 255, 0.2); }由于默认样式位于 index.css 文件中,因此加载它们是可选的:如果您跳过导入并编写自己的 [data-dnd-state="…"] 规则,EffDND 将完全摆脱依赖,让您拥有完全的视觉控制权。
提示: 原始元素是通过
opacity属性变暗,而不是被移除——因此列表保持其布局,并且 拖动时,同级元素不会跳动。如果您希望原始元素完全消失,请将passive状态的opacity设置为 0(或将visibility: hidden设置为 0)。
简明参考
每个属性及其参数的精简版。
| 属性 / 参数 | 值 | 默认值 | 用途 |
|---|---|---|---|
data-dnd-trigger | — | — | 标记开始拖动的手柄 |
… dist | 数字 | — | 指针移动指定像素后才拖动 |
… axis | x / y | both | 将拖动锁定在一个轴上 |
… scope | 名称 / * | nearest in DOM | 使用特定容器(或忽略作用域) |
… item | 名称 | nearest in DOM | 拖动特定项 |
… target | 名称 | all | 仅允许拖放到名称匹配的目标上 |
data-dnd-disabled | — | — | 禁用触发器 |
data-dnd-item | 唯一名称 | — | 标记被拖动的元素 |
data-dnd-scope | 唯一名称 | — | 限制拖放操作只能在一个容器内进行 |
data-dnd-reorder | x / y | — | 将容器转换为可排序列表 |
data-dnd-target | 名称 | — | 标记放置区域 |
data-dnd-transfer | append / prepend / remove | — | 放置在放置区域时执行的操作 |
data-dnd-scroll | threshold;speed | 30;10 | 拖动时自动滚动容器 |
data-dnd-transition | 持续时间和缓动 | 100ms linear | 平滑移动动画 |
data-dnd-state | active / passive | — | 运行时拖动状态,通过 CSS 设置样式(参见样式) |