這是我參與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 介紹和進階操做(容器間的拖拽 & 容器可滾動),看這邊。