React 實現列表拖動效果

React 實現列表拖動效果

當咱們想在 React 中實現一個列表拖動的效果的時候,有不少的第三方庫(React dnd)能夠借鑑,可是學習第三方庫也是一個成本,或者拖動自己並不複雜,只須要第三方庫的某一個 api 。這樣狀況下,咱們能夠本身實現一個。html

組件源碼

效果預覽 用戶名:admin;密碼:admin;react

1. 使用 React 的鼠標事件

React 鼠標事件git

在這裏咱們只會用到:onMouseDownonMouseMoveonMouseUp 這三個鼠標事件github

定義組件的 stateapi

state = {
    list: data, // 列表的數據
    dragging: false, // 是否開始拖動
    draggingIndex: -1, // 拖動元素的下標
    startPageY: 0, // 開始拖動的 Y 軸座標
    offsetPageY: 0 // 拖動元素的位移
  }
複製代碼

2. 判斷拖放的開始和結束

onMouseDown開始、onMouseUp結束數組

  1. 給列表的每一項添加一個onMouseDown事件監聽
<List.Item 
    onMouseDown={(e) => this.dragging(e, index)}
    >
  {item}
</List.Item>
複製代碼
  1. 當鼠標按下的時候咱們初始化組件的狀態。
// 點擊的時候記錄 Y 軸的位置 
dragging = (e, index) => {
  this.setState({
    dragging: true, 
    draggingIndex: index, 
    currentPageY: e.pageY, // 只須要縱向移動 
    startPageY: e.pageY,
  })
}
複製代碼
  1. 給點擊的元素添加樣式,讓咱們知道要拖動誰。
<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
  }
}
複製代碼

3. 實現拖放元素的位移效果

效果:拖放的元素視覺效果上要脫離列表自己,在列表上下進行移動。學習

如何實現:動畫

  1. 須要對onMouseMove進行監聽。this

    1. 在哪裏進行監聽 ?列表自己?spa

      首先不能在列表自己進行監聽,也不能在父容器監聽,由於鼠標移動的範圍是屏幕的大小(暫時這麼設定),那麼只有在 document 上進行監聽嗎 ?

      其實咱們能夠設置一個遮罩層,在它上面進行onMouseMoveonMouseUp的事件監聽。

      <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)'
      }
      複製代碼
  2. 須要記錄 onMouseMove 移動的軌跡(offset),列表跟隨鼠標進行移動。

    1. 須要記錄從起點移動到終點的距離(offset),記錄移動元素的下標。
    2. 根據移動距離是否大於行高判斷是向上移動,仍是向下移動。
    3. 移動的過程更新 offsetPageY 而後藉助 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 })
}
複製代碼

4. 更新拖放後的數據

移動的過程當中更新列表的數據

// 從新計算數組,插入一個,刪除一個。不斷地插入刪除(每隔一行)。
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 。以此類推。相似冒泡排序。

5. 結束拖放

// 鬆開鼠標的時候,從新初始化 startPageY、draggingIndex,
onMouseUp = (e) => {
  this.setState({
    dragging: false,// 移除遮罩
    startPageY: 0, 
    draggingIndex: -1
  })
}
複製代碼

這只是一個很簡單場景實現(列表的簡單排序),如需功能強大的拖動仍是須要第三方庫來實現。

相關文章
相關標籤/搜索