当咱们想在 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
})
}
复制代码
这只是一个很简单场景实现(列表的简单排序),如需功能强大的拖动仍是须要第三方库来实现。