点击上方 前端Q,关注公众号
回复加群,加入前端Q技术交流群
一、用法简介
基于react的拖拽功能,有这么几个比较流行的库:
react-dnd
react-beautiful-dnd
dnd-kit
react-sortable-hoc
React-dnd
(一)基本概念
Backend:后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action,你可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现
Item:用一个数据对象来描述当前被拖拽的元素,例如{ cardId: 42 }
Type:类似于 redux 里面的actions types 枚举常量,定义了应用程序里支持的拖拽类型
Monitor: 拖放本质上是有状态的。要么正在进行拖动操作,要么不在。要么有当前类型和当前项目,要么没有,React DnD 通过 Monitor 来存储这些状态并且提供查询
Connector:连接组件和 Backend ,可以让 Backend 获取到 DOM
DragSource:这是一个高阶组件,使用它包裹住你的组件使它变为拖拽源
DropTarget:这是一个高阶组件,使用它包裹住你的组件使它变为放置源
DragDropContext:包裹根组件,提供拖拽的上下文环境
(二)简单demo
![91f5441e8cd5a791ddcd8d937d012338.jpeg](https://img-blog.csdnimg.cn/img_convert/91f5441e8cd5a791ddcd8d937d012338.jpeg)
codesandbox.io/s/github/re…[1]
react-beautiful-dnd
(一)基本概念
![ae7ea7004ffc06780e818b65029231d1.gif](https://img-blog.csdnimg.cn/img_convert/ae7ea7004ffc06780e818b65029231d1.gif)
![11dd3d37d871fab3b9bd5671627f34cc.jpeg](https://img-blog.csdnimg.cn/img_convert/11dd3d37d871fab3b9bd5671627f34cc.jpeg)
主要包含三个组件.
DragDropContext : 用于包装拖拽根组件,Draggable和Droppable都需要包裹在DragDropContext内
Draggable 用于包装你需要拖动的组件,使组件能够被拖拽(make it draggable)
Droppable 用于包装接收拖拽元素的组件,使组件能够放置(dropped on it)
(二)简单demo
![e80ee9edf2f83834f0ac72d3613c81ef.jpeg](https://img-blog.csdnimg.cn/img_convert/e80ee9edf2f83834f0ac72d3613c81ef.jpeg)
![a1340949213b7d5220908abcf20d0568.jpeg](https://img-blog.csdnimg.cn/img_convert/a1340949213b7d5220908abcf20d0568.jpeg)
https://juejin.cn/post/codesandbox.io/s/k260nyxq9v
dnd-kit
(一)、基本概念
![f69ae12c58a7f5fe15f1ec5fa0910067.jpeg](https://img-blog.csdnimg.cn/img_convert/f69ae12c58a7f5fe15f1ec5fa0910067.jpeg)
![3616e35e90249fa4e892dbe31fbadfd7.jpeg](https://img-blog.csdnimg.cn/img_convert/3616e35e90249fa4e892dbe31fbadfd7.jpeg)
1、DndContext 用于包装拖拽根组件,Draggable和Droppable都需要包裹在DndContext 内
2、Droppable 用于包装接收拖拽元素的组件,使组件能够放置
3、Draggable 用于包装你需要拖动的组件,使组件能够被拖拽
4、Sensors 用于检测不同的输入方法,以启动拖动操作、响应移动以及结束或取消操作,内置传感器有:
指针
鼠标
触摸
键盘
5、Modifiers 可让您动态修改传感器检测到的运动坐标。它们可用于广泛的用例,例如:
将运动限制在单个轴上
限制可拖动节点容器的边界矩形的运动
限制可拖动节点的滚动容器边界矩形的运动
施加阻力或夹紧运动
(二)、简单demo
![a4b1101e6e713ba3ddddb6d88190c227.jpeg](https://img-blog.csdnimg.cn/img_convert/a4b1101e6e713ba3ddddb6d88190c227.jpeg)
5fc05e08a4a65d0021ae0bf2-ffprtowwny.chromatic.com/iframe.html…[2]
react-sortable-hoc
(一)、基本概念
1、SortableContainer 拖拽排序的容器
2、SortableElement 拖拽排序的元素
(二)、简单demo
![ba0b2831cc0b958be5dda667db569455.jpeg](https://img-blog.csdnimg.cn/img_convert/ba0b2831cc0b958be5dda667db569455.jpeg)
https://juejin.cn/post/codesandbox.io/s/react-sortable-hoc-starter-o104x95y86
三、兼容antd的table
如何配合antd的table组件进行使用?
![7f2d7e6cbd575f8e8ccc5daff40c06f9.jpeg](https://img-blog.csdnimg.cn/img_convert/7f2d7e6cbd575f8e8ccc5daff40c06f9.jpeg)
react-dnd 使用antd-table :codesandbox.io/s/tuo-zhuai…[3]
![2657d50d2fea137c190ce396445cc045.jpeg](https://img-blog.csdnimg.cn/img_convert/2657d50d2fea137c190ce396445cc045.jpeg)
react-sortable-hoc使用antd-table:codesandbox.io/s/tuo-zhuai…[4]
![e15e5b242a930ccf3dced22ebf98b274.jpeg](https://img-blog.csdnimg.cn/img_convert/e15e5b242a930ccf3dced22ebf98b274.jpeg)
react-beautiful-dnd-antd-table:codesandbox.io/s/react-bea…[5]
dnd-kit:stackblitz.com/edit/react-…[6](该demo无法运行,github.com/clauderic/d…[7] 根据这个issue说是antd-design自身的原因,
![758fb3c665594972a9eae5db22ec3c50.png](https://img-blog.csdnimg.cn/img_convert/758fb3c665594972a9eae5db22ec3c50.png)
在底层增强了表格组件)
四、树兼容
antd自带的tree拖拽排序:
codepen.io/huxinmin/em…[8]
自带的tree拖拽缺点是
无法实现动态实时拖拽更换位置效果,必须拖拽结束后才发生位置变化
需要修改大量的自带的样式
可以简单地把树看做是互相嵌套的列表。
![efcf9cafd8c72d28971f4e2691bc1f1d.jpeg](https://img-blog.csdnimg.cn/img_convert/efcf9cafd8c72d28971f4e2691bc1f1d.jpeg)
react-dnd:codesandbox.io/s/crazy-hoo…[9]
![1f4b184727230e6127cac84220a55c95.jpeg](https://img-blog.csdnimg.cn/img_convert/1f4b184727230e6127cac84220a55c95.jpeg)
react-sortable-hoc:codesandbox.io/embed/react…[10]
![0064a47354a98a24b18ab5d9408a92be.jpeg](https://img-blog.csdnimg.cn/img_convert/0064a47354a98a24b18ab5d9408a92be.jpeg)
react-beautiful-dnd-antd-table:codesandbox.io/embed/react…[11]
![22bcecb498e10ee8d279fb204097dd26.jpeg](https://img-blog.csdnimg.cn/img_convert/22bcecb498e10ee8d279fb204097dd26.jpeg)
dnd-kit:codesandbox.io/embed/react…[12]
五、移动端兼容
![f063b009f0640360a9c2c472be4bbddd.jpeg](https://img-blog.csdnimg.cn/img_convert/f063b009f0640360a9c2c472be4bbddd.jpeg)
react-dnd:codesandbox.io/embed/react…[13]
![55d1fa0a7e325ebc148207804b20c20e.jpeg](https://img-blog.csdnimg.cn/img_convert/55d1fa0a7e325ebc148207804b20c20e.jpeg)
react-beautiful-dnd-antd-table:codesandbox.io/s/react-bea…[14]
![0e06f54fa4d5cf090ad0bb8a25b8113f.jpeg](https://img-blog.csdnimg.cn/img_convert/0e06f54fa4d5cf090ad0bb8a25b8113f.jpeg)
react-sortable-hoc使用antd-table:codesandbox.io/s/tuo-zhuai…[15]
![f7cd45b2f495212bb327467942accc27.jpeg](https://img-blog.csdnimg.cn/img_convert/f7cd45b2f495212bb327467942accc27.jpeg)
dnd-kit:5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html…[16]
六、无限滚动
最佳的方法就是使用virtual-list,不过这几个库的支持情况也不一样。
![5f511290e58c8da81638a8b428b73235.jpeg](https://img-blog.csdnimg.cn/img_convert/5f511290e58c8da81638a8b428b73235.jpeg)
react-dnd:codesandbox.io/embed/react…[17]
使用requestAnimationFrame进行性能优化,也可以配合其他的虚拟list库进行使用。
![7383dbed560f2706137a45d938f842aa.jpeg](https://img-blog.csdnimg.cn/img_convert/7383dbed560f2706137a45d938f842aa.jpeg)
dnd-kit:5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/iframe.html…[18]
这个demo使用了react-tiny-virtual-list[19]库。
![aa61e0a5ab2c97ed08f91694f681db5c.jpeg](https://img-blog.csdnimg.cn/img_convert/aa61e0a5ab2c97ed08f91694f681db5c.jpeg)
react-sortable-hoc:clauderic.github.io/react-sorta…[20]
使用了react-virtualized
![f4d05ce1a75431d746fc95158c89ca51.jpeg](https://img-blog.csdnimg.cn/img_convert/f4d05ce1a75431d746fc95158c89ca51.jpeg)
react-beautifule-dnd:react-beautiful-dnd.netlify.app/iframe.html…[21]
使用了react-virtualized。
七、总结对比
react-dnd[22]
文档齐全
github star星数16.4k
维护更新良好,最近一月内有更新维护
学习成本较高
功能中等
移动端兼容情况,良好
示例数量中等
概念较多,使用复杂
组件间能解耦
react-beautiful-dnd[23]
文档齐全
github star星数24.8k
维护更新良好,最近三月内有更新维护
学习成本较高
使用易度中等
功能丰富
移动端兼容情况,优秀
示例数量丰富
是为垂直和水平列表专门构建的更高级别的抽象,没有提供 react-dnd 提供的广泛功能
外观漂亮,可访问性好,物理感知让人感觉更真实的在移动物体
开发理念上是拖拽,不支持copy/clone
dnd-kit[24]
文档齐全
github star星数2.8k
维护更新良好,最近一月内有更新维护
学习成本中等
使用易度中等
功能中等
移动端兼容情况,中等
示例数量丰富
未看到copy/clone
react-sortable-hoc[25]
文档较少
github star星数9.5k
维护更新良好,最近三月内有更新维护
学习成本较低
使用易度较低
功能简单
移动端兼容情况,中等
示例数量中等
不支持拖拽到另一个容器中
未看到copy/clone
主要集中于排序功能,其余拖拽功能不丰富
如果是要结合antd的table使用,最简单的组件是react-sortable-hoc,如果是无限滚动react-sortable-hoc示例虽然多,但是源码很少,可以考虑使用react-beautiful-dnd。如果是树形拖拽,要求不高的情况可以使用antd自带的tree,要求高点可以使用react-beautiful-dnd。兼容移动端,可以考虑使用react-sortable-hoc或者react-beautiful-dnd。
八、如何自己封装一个简单的拖拽组件
一、HTML5拖放API
首先,为了使元素可以拖动,需要设置draggable属性:
<img draggable="true">
然后有这么几个拖拽处理的函数:
ondrag 拖放进行中
ondragend/ondragstart 开始拖放和结束拖放
ondragover 当元素或选中的文本被拖到一个目标目标上(每100毫秒触发一次)。
ondragenter/ondragleave 源对象开始进入/离开目标对象范围内
ondrop 源对象被拖放到目标对象上
数据的传输,使用event.dataTransfer,它有如下这些api:
setData: 添加拖拽数据,这个方法接收两个参数,第一个参数是数据类型(可自定义),第二个参数是对应的数据
getData:反向操作,获取数据,只接收一个参数,即数据类型
clearData: 清除数据
setDragImage: 可自定义拖放过程中鼠标旁边的图像
effectAllowed: 属性指定拖放操作所允许的一个效果。copy 操作用于指示被拖动的数据将从当前位置复制到放置位置。_move操作用于指定被拖动的数据将被移动。link_操作用于指示将在源和放置位置之间创建某种形式的关系或连接。
二、功能与架构设计
使用react-hooks
拖拽对象drag组件,拖放对象drop组件,拖拽上下文dndContext
支持移动端
支持排序
三、代码
Drag组件使用:
<Drag index={1} id='1'>
<div>被包裹的可以拖拽的组件</div>
</Drag>
Drag组件实现:
import { FC } from "react";
interface DragProps {
index: number;
id: string | number;
}
const Drag: FC<DragProps> = (props) => {
const startDrag = (ev) => {
// 传输数据
ev.dataTransfer.setData("index", props.index);
ev.dataTransfer.setData("id", props.id);
};
return (
<div draggable onDragStart={startDrag}>
{props.children}
</div>
);
};
export default Drag;
Drop组件使用:
<Drop>
<Drag index={1} id='1'>
<div>被包裹的可以拖拽的组件</div>
</Drag>
</Drop>
Drop组件实现:
import { FC, useContext } from "react";
import { Context } from "./DndContext";
const Drop: FC = (props) => {
const { onDragOver, onDragEnd } = useContext(Context);
const dragOver = (ev) => {
ev.preventDefault();
if (onDragOver) onDragOver();
};
const drop = (ev) => {
// 获取数据
const oldIndex = ev.dataTransfer.getData("index");
// 获取拖拽结束时的Y轴坐标
const Y = ev.clientY;
// 简便计算,设定高度为20
// 我这里很偷懒,实际计算情况很复杂
//一般有两种实现思路,一种就是根据位置计算,另外一种就是给拖拽源设置可放置,然后获取
const height = 20;
const newIndex = Math.floor(Y / height);
if (oldIndex) {
if (onDragEnd) onDragEnd(Number(oldIndex), newIndex);
}
};
return (
<div onDragOver={dragOver} onDrop={drop}>
{props.children}
</div>
);
};
export default Drop;
DndContext组件使用:
<DndContext
onDragEnd={(oldIndex, newIndex) => {
setData(arrayMove(data, oldIndex, newIndex));
}}
onDragOver={() => {}}
>
<Drop>
{data.map((i, index) => (
<Drag key={i.id} id={i.id} index={index}>
<div className="item">{i.text}</div>
</Drag>
))}
</Drop>
</DndContext>
DndContext组件实现:
import { createContext, FC } from "react";
export interface TContext {
onDragOver: () => void;
onDragEnd: (oldIndex: number, newIndex: number) => void;
}
const Context = createContext<TContext>({} as TContext);
const DndContext: FC<TContext> = (props) => {
return (
<Context.Provider
value={{
onDragEnd: (oldIndex, newIndex) => {
props.onDragEnd(oldIndex, newIndex);
},
onDragOver: () => {
props.onDragOver();
}
}}
>
{props.children}
</Context.Provider>
);
};
export { Context };
export default DndContext;
如果需要处理移动端的兼容性,可以使用如下库:
github.com/timruffles/…[26]
四、优化空间
拖拽结束,所在位置计算
拖拽过程中实时交换位置
性能优化
异常处理等
拖拽过程样式
拖拽方向,x轴和Y轴
等等
五、在线代码
具体在线代码示例:codesandbox.io/embed/react…[27]
![3f518f39667dbc585b9d35af876e0882.jpeg](https://img-blog.csdnimg.cn/img_convert/3f518f39667dbc585b9d35af876e0882.jpeg)
作者:青火
文章地址:https://juejin.cn/post/7062625911312646175
往期推荐
你不知道的 async、await 魔鬼细节
速来!腾讯微信团队招人,简历直推面试官!
收藏!史上最全 Vue 前端代码风格指南
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...
点个在看支持我吧