评论

收藏

[JavaScript] 使用react-beautiful-dnd实现列表间拖拽踩坑

开发技术 开发技术 发布于:2021-06-28 16:20 | 阅读数:448 | 评论:0

为什么选用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
关注下面的标签,发现更多相似文章