當咱們想在 React 中實現一個列表拖動的效果的時候,有不少的第三方庫(React dnd)能夠借鑑,可是學習第三方庫也是一個成本,或者拖動自己並不複雜,只須要第三方庫的某一個 api 。這樣狀況下,咱們能夠本身實現一個。html
組件源碼效果預覽 用戶名:admin;密碼:admin;react
React 鼠標事件git
在這裏咱們只會用到:onMouseDown
、onMouseMove
、onMouseUp
這三個鼠標事件github
定義組件的 stateapi
state = {
list: data, // 列表的數據
dragging: false, // 是否開始拖動
draggingIndex: -1, // 拖動元素的下標
startPageY: 0, // 開始拖動的 Y 軸座標
offsetPageY: 0 // 拖動元素的位移
}
複製代碼
onMouseDown
開始、onMouseUp
結束數組
onMouseDown
事件監聽<List.Item
onMouseDown={(e) => this.dragging(e, index)}
>
{item}
</List.Item>
複製代碼
// 點擊的時候記錄 Y 軸的位置
dragging = (e, index) => {
this.setState({
dragging: true,
draggingIndex: index,
currentPageY: e.pageY, // 只須要縱向移動
startPageY: e.pageY,
})
}
複製代碼
<List.Item
onMouseDown={(e) => this.dragging(e, index)}
style={this.getDraggingStyle(index)}
>
{item}
</List.Item>
複製代碼
// 移動動畫
getDraggingStyle = (index) => {
if (index !== this.state.draggingIndex) return
return {
backgorundColor: '#eee',
transform: `translate(10px,${this.state.offsetPageY}px)`, // 下面會介紹
opacity: 0.5
}
}
複製代碼
效果:拖放的元素視覺效果上要脫離列表自己,在列表上下進行移動。學習
如何實現:動畫
須要對onMouseMove
進行監聽。this
在哪裏進行監聽 ?列表自己?spa
首先不能在列表自己進行監聽,也不能在父容器監聽,由於鼠標移動的範圍是屏幕的大小(暫時這麼設定),那麼只有在 document
上進行監聽嗎 ?
其實咱們能夠設置一個遮罩層,在它上面進行onMouseMove
、onMouseUp
的事件監聽。
<List
dataSource={this.state.list}
renderItem={(item, index) => (
<List.Item onMouseDown={(e) => this.dragging(e, index)} key={item} style={this.getDraggingStyle(index)} > {item} </List.Item> )} /> {/* 用一個遮罩監聽事件,也能夠監聽整個 document */} { this.state.dragging && ( <div style={maskStyle} onMouseUp={e => this.onMouseUp(e)} onMouseMove={e => this.onMouseMove(e)} > </div> ) } 複製代碼
const maskStyle = {
position: 'fixed',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgorund: 'rgba(0,0,0,0.5)'
}
複製代碼
須要記錄 onMouseMove
移動的軌跡(offset),列表跟隨鼠標進行移動。
CSS3
動畫進行移動。// 移動的軌跡
onMouseMove = (e) => {
let offset = e.pageY - this.state.startPageY
const draggingIndex = this.state.draggingIndex
if (offset > lineHeight && draggingIndex < this.state.list.length) {
// 向下移動
offset -= lineHeight
} else if (offset < -lineHeight && draggingIndex > 0) {
// 向上移動
offset += lineHeight
}
// item 移動的距離
this.setState({ offsetPageY: offset })
}
複製代碼
移動的過程當中更新列表的數據
// 從新計算數組,插入一個,刪除一個。不斷地插入刪除(每隔一行)。
const move = (arr = [], startIndex, toIndex) => {
arr = arr.slice()
arr.splice(toIndex, 0, arr.splice(startIndex, 1))
return arr;
}
複製代碼
更新數據
onMouseMove = (e) => {
let offset = e.pageY - this.state.startPageY
const draggingIndex = this.state.draggingIndex
if (offset > lineHeight && draggingIndex < this.state.list.length) {
// 向下移動
offset -= lineHeight
// 按照移動的方向進行數據交換
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex + 1),
draggingIndex: draggingIndex + 1,
startPageY: this.state.startPageY + lineHeight
})
} else if (offset < -lineHeight && draggingIndex > 0) {
// 向上移動
offset += lineHeight
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex - 1),
draggingIndex: draggingIndex - 1,
startPageY: this.state.startPageY - lineHeight
})
}
複製代碼
向上移動 draggingIndex-1 ,向下移動 draggingIndex+1 。
每移動一行,數據更新一次,0 移動到 2 的位置,須要先移動到 1 ,而後從 1 的位置開始從新計算位移,而後由 1 的位置移動到 2 。以此類推。相似冒泡排序。
// 鬆開鼠標的時候,從新初始化 startPageY、draggingIndex,
onMouseUp = (e) => {
this.setState({
dragging: false,// 移除遮罩
startPageY: 0,
draggingIndex: -1
})
}
複製代碼
這只是一個很簡單場景實現(列表的簡單排序),如需功能強大的拖動仍是須要第三方庫來實現。