React中使用dnd-kit实现拖曳排序功能 |
|
由于前阵子需要在开发 Picals 的时候,需要实现一些拖动排序的功能 。虽然有原生的浏览器 dragger API,不过纯靠自己手写很难实现自己想要的效果,更多的是吃力不讨好 。于是我四处去调研了一些 React 中比较常用的拖曳库,最终确定了 当然,使用的时候肯定免不了踩坑 。这篇文章的意义就是为了记录所踩的坑,希望能够为有需要的大家提供一点帮助 。 在这篇文章中,我将带着大家一起实现如下的拖曳排序的例子:
那让我们开始吧 。 安装安装 pnpm add @dnd-kit/core @dnd-kit/sortable @dnd-kit/modifiers @dnd-kit/utilities 这几个包分别有什么作用呢?
使用方法首先我们需要知道的是,拖曳这个行为需要涉及到两个部分:
在使用 父容器(DraggableList)的编写我们首先进行拖曳父容器相关的功能配置 。话不多说我们直接上代码: import { FC, useEffect, useState } from "react";
import type { DragEndEvent, DragMoveEvent } from "@dnd-kit/core";
import { DndContext } from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
rectSortingStrategy,
} from "@dnd-kit/sortable";
import { restrictToParentElement } from "@dnd-kit/modifiers";
import "./index.scss";
import DraggableItem from "../draggable-item";
type ImgItem = {
id: number;
url: string;
};
const DraggableList: FC = () => {
const [list, setList] = useState<ImgItem[]>([]);
useEffect(() => {
setList(
Array.from({ length: 31 }, (_, index) => ({
id: index + 1,
url: String(index),
}))
);
}, []);
const getMoveIndex = (array: ImgItem[], dragItem: DragMoveEvent) => {
const { active, over } = dragItem;
const activeIndex = array.findIndex((item) => item.id === active.id);
const overIndex = array.findIndex((item) => item.id === over?.id);
// 处理未找到索引的情况
return {
activeIndex: activeIndex !== -1 ? activeIndex : 0,
overIndex: overIndex !== -1 ? overIndex : activeIndex,
};
};
const dragEndEvent = (dragItem: DragEndEvent) => {
const { active, over } = dragItem;
if (!active || !over) return; // 处理边界情况
const moveDataList = [...list];
const { activeIndex, overIndex } = getMoveIndex(moveDataList, dragItem);
if (activeIndex !== overIndex) {
const newDataList = arrayMove(moveDataList, activeIndex, overIndex);
setList(newDataList);
}
};
return (
<DndContext onDragEnd={dragEndEvent} modifiers={[restrictToParentElement]}>
<SortableContext
items={list.map((item) => item.id)}
strategy={rectSortingStrategy}
>
<div className="drag-container">
{list.map((item) => (
<DraggableItem key={item.id} item={item} />
))}
</div>
</SortableContext>
</DndContext>
);
};
export default DraggableList;
对应的 .drag-container {
position: relative;
width: 800px;
display: flex;
flex-wrap: wrap;
gap: 20px;
}
return 的 DOM 元素结构非常简单,最主要的无外乎两个上下文组件:
在
对于
可以看到,不仅是结束回调,也接受拖曳全过程的函数回调并通过回传值进行一些数据处理 。 但是,一般用于完成拖曳排序功能我们可以不管这么多,只用管鼠标松开后的回调函数,然后拿到对象进行处理就可以了 。
const dragEndEvent = (dragItem: DragEndEvent) => {
const { active, over } = dragItem;
if (!active || !over) return; // 处理边界情况
const moveDataList = [...list];
const { activeIndex, overIndex } = getMoveIndex(moveDataList, dragItem);
if (activeIndex !== overIndex) {
const newDataList = arrayMove(moveDataList, activeIndex, overIndex);
setList(newDataList);
}
};
首先检查
const getMoveIndex = (array: ImgItem[], dragItem: DragMoveEvent) => {
const { active, over } = dragItem;
const activeIndex = array.findIndex((item) => item.id === active.id);
const overIndex = array.findIndex((item) => item.id === over?.id);
// 处理未找到索引的情况
return {
activeIndex: activeIndex !== -1 ? activeIndex : 0,
overIndex: overIndex !== -1 ? overIndex : activeIndex,
};
};
接下来是对
这也就意味着,我们在 items 这边传入了什么数组来对排序列表进行唯一性表示,active 和 over 就按照什么来追踪元素的排序索引 。
至此,父容器组件介绍完毕,我们来看子元素怎么写吧 。 子元素(Draggable-item)的编写上代码: import { FC } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import "./index.scss";
type ImgItem = {
id: number;
url: string;
};
type DraggableItemProps = {
item: ImgItem;
};
const DraggableItem: FC<DraggableItemProps> = ({ item }) => {
const { setNodeRef, attributes, listeners, transform, transition } =
useSortable({
id: item.id,
transition: {
duration: 500,
easing: "cubic-bezier(0.25, 1, 0.5, 1)",
},
});
const styles = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
style={styles}
className="draggable-item"
>
<span>{item.url}</span>
</div>
);
};
export default DraggableItem;
对应的 .draggable-item {
width: 144px;
height: 144px;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
font-size: large;
cursor: pointer;
user-select: none;
border-radius: 10px;
overflow: hidden;
}
子元素的编写相较于父容器要简单得多,需要手动配置的少,引入的包更多了 。 首先是引入了
它接受一个配置对象,其中包含了:
之后我们定义了拖曳样式 之后就是直接一股脑的将配置全部传入要真正进行拖曳的 DOM 元素: return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
style={styles}
className="draggable-item"
>
<span>{item.url}</span>
</div>
);
};
实现效果父容器和子元素全都编写完毕后,我们可以观察一下总体的实现效果如何:
可以看到,元素已经能够正常地被排序,而且列表也能够同样地被更新 。结合到具体的例子,可以把这个列表 item 结合更加复杂的类型进行处理即可 。只要保证每个 item 有唯一的 id 即可 。 对于原有点击事件失效的处理对于某些需要触发点击事件的拖曳 item,如果按照上述方式封装了拖曳子元素所需的一些配置,那么 原有的点击事件将会失效,因为原有的鼠标按下的点击事件被拖曳事件给覆盖掉了 。当然,dnd-kit 肯定也是考虑到了这种情况 。他们在其核心库 使用方法也非常简单,首先从核心库中导入这个 hook,之后进行如下的配置: //拖拽传感器,在移动像素5px范围内,不触发拖拽事件
const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint: {
distance: 5,
},
})
);
这里配置了在 5px 范围内不触发拖曳事件,这样就可以在这个范围内进行点击事件的正常触发了 。 在上面的 <DndContext onDragEnd={dragEndEvent} modifiers={[restrictToParentElement]}>
<SortableContext
items={list.map((item) => item.id)}
strategy={rectSortingStrategy}
sensors={sensors}
>
<div className="drag-container">
{list.map((item) => (
<DraggableItem key={item.id} item={item} />
))}
</div>
</SortableContext>
</DndContext>
以上就是React中使用dnd-kit实现拖曳排序功能的详细内容,更多关于React dnd-kit拖曳排序的资料请关注其它相关文章! |