这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战css
拖放 在实际业务中也是一个常见的功能,好比经过拖放列表元素进行排序、拖放元素在不一样的容器中穿梭等。提到拖放,咱们想到的是原生 DOM 事件的操做、元素移动时位置的计算以及动画等等,第一反应必然是找第三方拖放库 ‘帮助’ 咱们完成需求咯。然而,第三方库虽然功能强大,但不少功能你未必须要,杀鸡焉用牛刀呢?react
因此咱们能够根据实际场景的复杂度来选择实现方式,第三方库的强大功能 or 本身实现最精炼的代码。git
实现一个列表拖放排序的功能,选中元素后,元素实时跟随鼠标移动,而且列表实时更新,以下图: github
在 React 中实现拖放的技术要点:npm
MouseDown
、MouseUp
、MouseMove
MouseDown
)和判断拖放结束(MouseUp
)在行元素上监听 Mousedown 事件,在 Mousedown 中初始化拖放状态:markdown
<li onMouseDown={(evt) => handleMounseDown(evt, i)} style={getDraggingStyle(i)}>
{text}
</li>
复制代码
const handleMounseDown = (evt, index) => {
setDragging(true);
setStartPageY(evt.pageY);
setDraggingIndex(index);
};
复制代码
样式上高亮拖放元素而且添加跟随鼠标效果,translateX
能够产生视觉上悬浮的效果,其中的 offsetPageY
的值将会在拖放过程当中(MouseMove
)计算得来:antd
const getDraggingStyle = (index) => {
if (index !== draggingIndex) return {};
return {
backgroundColor: 'lightsteelblue',
transform: `translate(10px, ${offsetPageY}px)`,
};
};
复制代码
拖放中最关键的地方是:该在哪一个节点上监听 MouseMove
事件?ide
用户拖动时,鼠标是能够全页面跑的,若是在拖动元素( <li/>
)或者父元素( <ul/>
)上监听事件都不合适。此时有一个方案:建立一个透明的全局的 fixed mask
,既覆盖了鼠标的运动轨迹,又防止触发其余元素。oop
{
dragging && (
<div className="my-dnd-mask" onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} />
);
}
复制代码
.my-dnd-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
}
复制代码
在 MouseMove 中,就是核心计算代码:post
let offset = evt.pageY - startPageY;
move
),并从新设置 DraggingIndex
、StartPageY
。offsetPageY
const move = (arr, startIndex, toIndex) => {
arr = arr.slice();
arr.splice(toIndex, 0, arr.splice(startIndex, 1)[0]);
return arr;
};
const handleMouseMove = (evt) => {
let offset = evt.pageY - startPageY;
if (offset > lineHeight && draggingIndex < list.length - 1) {
// move down
offset -= lineHeight;
setList((pre) => move(pre, draggingIndex, draggingIndex + 1));
setDraggingIndex((pre) => pre + 1);
setStartPageY((pre) => pre + lineHeight);
} else if (offset < -lineHeight && draggingIndex > 0) {
// move up
offset += lineHeight;
setList((pre) => move(pre, draggingIndex, draggingIndex - 1));
setDraggingIndex((pre) => pre - 1);
setStartPageY((pre) => pre - lineHeight);
}
setOffsetPageY(offset);
};
复制代码
设置 dragging: false
,mask 层消失, startPageY
、draggingIndex
初始化。
const handleMouseUp = () => {
setDragging(false);
setStartPageY(0);
setDraggingIndex(-1);
};
复制代码
完整代码能够看 这里
找了一个在 React 最有名的拖放库,react-beautiful-dnd 。
# yarn
yarn add react-beautiful-dnd
# npm
npm install react-beautiful-dnd --save
复制代码
使用 react-beautiful-dnd
:
import { DragDropContext } from 'react-beautiful-dnd';
复制代码
须要咱们管理的部分:
其他的事情,react-beautiful-dnd
会办好的。
<DragDropContext onDragEnd={onDragEnd}>
<center> <Droppable droppableId="droppable"> {(provided, snapshot) => { return ( <div ref={provided.innerRef}> {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef}>{item.content}</div> )} </Draggable> ))} {provided.placeholder} </div> ); }} </Droppable> </center>
</DragDropContext>
复制代码
// 元素移动
const move = (arr, startIndex, toIndex) => {
arr = arr.slice();
arr.splice(toIndex, 0, arr.splice(startIndex, 1)[0]);
return arr;
};
const onDragEnd = (result) => {
if (!result.destination) {
return;
}
setItems((pre) => move(pre, result.source.index, result.destination.index));
};
复制代码
// 设置样式
const getItemStyle = (isDragging, draggableStyle) => ({
userSelect: 'none',
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
// 拖拽的时候,item 背景变化
background: isDragging ? 'lightgreen' : '#ffffff',
...draggableStyle,
});
const getListStyle = (isDraggingOver) => {
return {
// 拖拽的时候,list 背景变化
background: isDraggingOver ? 'darkgreen' : 'gray',
padding: 8,
width: 250,
};
};
<Droppable droppableId="droppable"> {(provided, snapshot) => { return ( <div style={getListStyle(snapshot.isDraggingOver)}> {items.map((item, index) => ( <Draggable> {(provided, snapshot) => ( <div style={getItemStyle( snapshot.isDragging, provided.draggableProps.style, )} ></div> )} </Draggable> ))} </div> ); }} </Droppable>;
复制代码
完整代码能够看 这里
详细的 API 介绍和进阶操做(容器间的拖拽 & 容器可滚动),看这边。