使用react-beautiful-dnd實現列表間拖拽踩坑

為什麼選用react-beautiful-dnd

相比於react-dnd,react-beautiful-dnd更適用於列表之間拖拽的場景,支持移動端,且較為容易上手。

基本使用方法

基本概念

  • DragDropContext:構建一個可以拖拽的范圍
  • onDragStart:拖拽開始回調
  • onDragUpdate:拖拽中的回調
  • onDragEnd:拖拽結束時的回調
  • Droppable – 可以放置拖拽塊的區域
  • Draggalbe – 可被拖拽的元素

使用方法

把你想能夠拖放的代碼放到DragDropContext中

import { DragDropContext } from 'react-beautiful-dnd';

class App extends React.Component {
  onDragStart = () => {
    /*...*/
  };
  onDragUpdate = () => {
    /*...*/
  }
  onDragEnd = () => {
    // the only one that is required
  };

  render() {
    return (
      <DragDropContext
        onDragStart={this.onDragStart}
        onDragUpdate={this.onDragUpdate}
        onDragEnd={this.onDragEnd}
      >
        <div>Hello world</div>
      </DragDropContext>
    );
  }
}

確定可放置區域Dropppable

import { DragDropContext, Droppable } from 'react-beautiful-dnd';

class App extends React.Component {
  // ...
  render() {
    return (
      <DragDropContext
        onDragStart={this.onDragStart}
        onDragUpdate={this.onDragUpdate}
        onDragEnd={this.onDragEnd}
      >
        <Droppable droppableId="droppable-1">
          {(provided, snapshot) => (
            <div
              ref={provided.innerRef}
              style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }}
              {...provided.droppableProps}
            >
              <h2>I am a droppable!</h2>
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    );
  }
}
  • 必需的DroppableId(字符串),用於唯一標識應用程序的droppable。不要更改此ID特別是在拖動時
  • provided.placeholder: 占位符(這個占位符是默認的,一般不咋符合需求)
  • snapshot: 當前拖動狀態,可以用來在被拖動時改變Droppable的外觀

在Dropppable區域使用Draggable包裹拖拽元素

import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

class App extends React.Component {
  // ...

  render() {
    return (
      <DragDropContext
        onDragStart={this.onDragStart}
        onDragUpdate={this.onDragUpdate}
        onDragEnd={this.onDragEnd}
      >
        <Droppable droppableId="droppable-1">
          {(provided, snapshot) => (
            <div
              ref={provided.innerRef}
              style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }}
              {...provided.droppableProps}
            >
              <Draggable draggableId="draggable-1" index={0}>
                {(provided, snapshot) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                    >
                      <h4>My draggable</h4>
                    </div>
                )}
              </Draggable>
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    );
  }
}
  • Draggable必須始終包含在Droppable中
  • DraggablebId(字符串):必須存在唯一ID,和index(如果為遍歷 key也需要)不要更改此ID,特別是在拖動時

拖拽結束時,改變源數據

onDragEnd = result => {
  const { source, destination, draggableId } = result;
  if (!destination) {
    return;
  }

  // 修改源和目標數組,將拖拽元素從源數組中刪除,再插入到目標數組中
  this.setState({
    xxx: xxx,
  });
}

使用過程中遇到的問題

向拖拽的目標區域增加自定義占位符(custom placeholder)

react-beautiful-dnd在拖拽到目標區域時,目標區域的元素之間會給當前拖拽元會自動空出一段space,這段space的距離是目標區域Draggable元素的大小(但不包括元素的margin邊距,這也是一個坑,下文會說到解決方法)。

因此可以在這段距離中采用絕對定位,增加自定義占位符。具體做法:計算出當前自定義占位符元素的left & top距離,在dragUpdate事件中更新這兩個距離,可參考beatiful-dnd-custom-placeholder-demo

拖拽時,修改拖拽元素的transform屬性,導致拖拽會卡死在某處,拖拽元素放置位置錯誤

在官方文檔中,有這樣一段說明, 大概是說draggable元素采用瞭position: fixed定位,但會受到transform會影響。

#### Warning: `position: fixed`

`react-beautiful-dnd` uses `position: fixed` to position the dragging element. This is quite robust and allows for you to have `position: relative | absolute | fixed` parents. However, unfortunately `position:fixed` is [impacted by `transform`](http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/) (such as `transform: rotate(10deg);`). This means that if you have a `transform: *` on one of the parents of a `<Draggable />` then the positioning logic will be incorrect while dragging. Lame! For most consumers this will not be an issue.

To get around this you can [reparent your <Draggable />](/docs/guides/reparenting.md). We do not enable this functionality by default as it has performance problems.

提供瞭如下解決方法:使用createPortal給拖動元素掛在空的父元素上,可參考issue: transform on parent messes up dragging positioning

但是這個方法並不能解決我的問題,因為還有自定義placeholder的需求。在拖拽時還需要計算placeholder的left的距離,也就需要獲取當前拖拽元素的parentNode下的子元素,使用createPortal則獲取不到拖拽元素的原parentNode,因此放棄createPortal的方案。采用改變width和height達到transform:scale的效果。

移動端拖拽元素需要長按該元素(long-press)

官方文檔中給出的說明是,在移動端場景下,在draggable元素上的手指操作,無法確定是tap,force press,或者scroll,所以需要長按該元素才能確定是拖拽。

Starting a drag: long press
A user can start a drag by holding their finger 👇 on an element for a small period of time 🕑 (long press)

拖拽某個元素懸停在目標位置時,空出的插入space距離不準確的問題
這個就是上文中提到的,Draggable之間留的placeholder的空餘距離是一個Draggable的距離,但不包括Dragglable的margin邊距,可參考這個issue。

最後采用padding來控制Draggable之間的距離,這樣在拖拽時空出的space就包括瞭padding。

總結

react-beautiful-dnd比較容易上手, 到2021年3月發佈瞭v13.1.0較為活躍, 以上踩過的坑,希望對大傢有所幫助。

參考資料

官網beautiful-dnd
react-beautiful-dnd入門教程

到此這篇關於使用react-beautiful-dnd實現列表間拖拽踩坑 的文章就介紹到這瞭,更多相關react 列表拖拽內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: