本文主要介绍一下React实现列表拖拽排序方法,具体样式如下图
首先,简单展示一下组件的数据结构
const CodeSetting = props => {
const {
$t, // 国际化翻译函数
vm, // 视图模型数据
vm: {
CodeSet: {
Enable = [], // 启用的编码列表
Disable = [] // 停用的编码列表
}
},
getConfig, // 获取配置的函数
save, // 保存配置的函数
vmChange // 更新视图模型的函数
} = props;
};
完整的数据结构示例
const vm = {
CodeSet: {
Enable: [
{ Compression: "H.264" },
{ Compression: "H.265" },
{ Compression: "MPEG-4" }
],
Disable: [
{ Compression: "AVC" },
{ Compression: "HEVC" }
]
}
};
主要用到的代码如下,简单看后我将介绍拖拽方法
{Enable.length ? (
<Card
title={`${$t('com.EnableCode')} (${Enable.length})`}
extra={
<Button
size='small'
className='clear-all-btn'
type='link'
onClick={clearAllEnabled}>
{$t('com.ClearAll')}
</Button>
}>
{Enable.map((item, index) => (
<div
key={index}
className='drag-item'
draggable
onDragStart={e => {
handleDragStart(e, index);
}}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDrop={e => handleDrop(e, index)}>
<div className='drag-handle'>⋮⋮</div>
<LabelText text={item.Compression} />
<div className='delete-btn-container'>
<Icon
component={remove}
onClick={() => codeSetChange('remove', index)}
style={{
fontSize: '20px'
}}
/>
</div>
</div>
))}
</Card>
首先用到的组件是Card组件,title是card标题,extra是card后缀
之后遍历Enable数组,将拿到的每一个值渲染到card中
这个组件实现了 HTML5 原生拖拽 API 来实现编码列表的拖拽排序功能。主要使用了以下拖拽事件:
onDragStart - 拖拽开始
onDragOver - 拖拽悬停
onDrop - 拖拽放置
onDragEnd - 拖拽结束
状态管理
const [draggedIndex, setDraggedIndex] = useState(null); // 记录当前拖拽项的索引
拖拽事件处理函数
1 拖拽开始 (handleDragStart)
const handleDragStart = (e, index) => {
setDraggedIndex(index); // 记录拖拽项的索引
e.dataTransfer.effectAllowed = 'move'; // 设置拖拽效果为移动
e.currentTarget.classList.add('dragging'); // 添加拖拽样式
};
2 拖拽悬停 (handleDragOver)
const handleDragOver = e => {
e.preventDefault(); // 阻止默认行为
e.dataTransfer.dropEffect = 'move'; // 设置放置效果为移动
// 清除所有拖拽项的悬停样式
const dragItems = document.querySelectorAll('.drag-item');
dragItems.forEach(item => {
item.classList.remove('drag-over');
});
// 为当前悬停元素添加悬停样式
e.currentTarget.classList.add('drag-over');
};
3 拖拽放置 (handleDrop)
const handleDrop = (e, dropIndex) => {
e.preventDefault();
e.currentTarget.classList.remove('drag-over');
if (draggedIndex === null || draggedIndex === dropIndex) {
return;
}
// 重新排序 Enable 数组
const enableList = [...Enable];
const draggedItem = enableList[draggedIndex];
// 移除拖拽项
enableList.splice(draggedIndex, 1);
// 在目标位置插入
enableList.splice(dropIndex, 0, draggedItem);
// 更新vm数据
const newCodeSet = {
...vm.CodeSet,
Enable: enableList
};
vmChange({ CodeSet: newCodeSet });
setDraggedIndex(null);
};
4 拖拽结束 (handleDragEnd)
const handleDragEnd = e => {
setDraggedIndex(null);
e.currentTarget.classList.remove('dragging');
// 清除所有拖拽项的悬停样式
const dragItems = document.querySelectorAll('.drag-item');
dragItems.forEach(item => {
item.classList.remove('drag-over');
});
};
5.JSX 结构
<div
key={index}
className='drag-item'
draggable // 设置为可拖拽
onDragStart={e => handleDragStart(e, index)} // 拖拽开始
onDragEnd={handleDragEnd} // 拖拽结束
onDragOver={handleDragOver} // 拖拽悬停
onDrop={e => handleDrop(e, index)}> // 拖拽放置
<div className='drag-handle'>⋮⋮</div> // 拖拽手柄
<LabelText text={item.Compression} />
<div className='delete-btn-container'>
{/* 删除按钮 */}
</div>
</div>
6. 核心算法
- 拖拽排序的核心算法是数组重排序:
- 获取拖拽项:从原位置取出拖拽的元素
- 移除拖拽项:在原位置删除该元素
- 插入新位置:在目标位置插入该元素
- 更新状态:将新的数组顺序更新到组件状态
7. 样式处理
组件通过 CSS 类名来管理拖拽状态:
.dragging - 拖拽中的样式
.drag-over - 拖拽悬停的样式
.drag-item - 可拖拽项的基础样式
8.样式代码
// 拖拽项容器
.drag-item {
display: flex;
align-items: center;
cursor: grab;
&:hover {
background-color: #f5f5f5;
}
// 拖拽中状态
&.dragging {
background-color: #e6f7ff;
opacity: 0.5;
transform: scale(0.95);
}
// 拖拽悬停状态
&.drag-over {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
}
}
// 拖拽手柄
.drag-handle {
margin-right: 8px;
color: #2f2e2e;
font-size: 12px;
user-select: none;
}