如何在 React 中拖放多个元素?

Posted

技术标签:

【中文标题】如何在 React 中拖放多个元素?【英文标题】:How to Drag & Drop Multiple Elements In React? 【发布时间】:2019-09-09 01:37:50 【问题描述】:

这是我在 *** 上的第一个问题。我想用 React 构建一个小游戏,用户可以将 tetrominos 拖放到网格上,还可以根据自己的喜好重新定位或旋转它们。 tetrominos 由一个矩阵表示,然后每个块都在一个 li 元素中呈现。

z-tetromino 的示例: [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0]

很遗憾,我还不能发布图片,这会让事情变得更容易。

网格过于用矩阵表示。

现在我要做的基本上是将这些块矩阵拖放到网格上,以便网格中的值相应地改变(0→1等)。

问题是,我不知道如何使用标准 html5 DnD API 或 React DnD 一次拖动多个 li 元素。当用户点击某个四联牌的一个 li 元素时,整块应该移动。也许我可以使用 jQuery UI 来解决这个问题,但是由于在 React 中不允许直接的 DOM 操作,我不知道该怎么做。

我尝试将一个块拖到半优化的网格上,因为一个块代替了一整行网格块,即使在 CSS 中设置了 display: inline-block。

这是第一次实验的一些简单代码。

onDragStart = e => 
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text', e.target.id);
    // e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
  ;

  handleDrop = e => 
    const pieceOrder = e.dataTransfer.getData('text');
    // console.log(document.getElementById(pieceOrder));
    // e.target.appendChild(document.getElementById(pieceOrder));
    // console.log(pieceOrder);
    e.target.replaceWith(document.getElementById(pieceOrder));
    e.target.remove();
  ;

renderEmptyBoardCell(i) 
    return (
      <li key=i className="emptyBoardCell" onDragOver=(e) => e.preventDefault() onDrop=(e) => this.handleDrop(e)>></li>
    );
  

  renderTemplateBoardCell(i) 
    return (
      <li key=i className="templateBoardCell" onDragOver=(e) => e.preventDefault() onDrop=(e) => this.handleDrop(e)>></li>
    );
  

  renderEmptyCell(i) 
    return (
      <li key=i className="emptyCell"></li>
    );
  

  renderFilledCell(piece_config, i) 
    return (
      <li key=i id=i className=`filledCell $piece_config` draggable onDragStart=this.onDragStart></li>
    );
  

所以问题是,理论上 React DnD 或任何其他库是否可行?如果是,那么一次 DnD 多个元素的近似解决方案是什么。

感谢您的宝贵时间!

【问题讨论】:

【参考方案1】:

使用 react-dnd 一次只能拖动一个项目。要么使用不同的库,要么先将不同的部分组合成一个项目,然后拖放该项目。

【讨论】:

好的,谢谢。您是否偶然知道任何能够做到这一点的图书馆。我认为说实话会更容易。 @Falic - 我不知道。我不熟悉其他拖放库。但我可以想象添加第二层(大部分是透明的)碎片。选择一块进行拖动会使一块仅在第二层中可见。然后你拖放整个第二层。不容易,但有可能。【参考方案2】:

如果有人在 2020 年寻找解决方案。这是我的 current solution 和 react-dnd 和 react 钩子。你可以试试现场演示here。

这是另一个更简单的例子,你可以查看codesandbox here。

【讨论】:

它完全符合我的要求。感谢您的评论。【参考方案3】:

我知道它有点晚了,但你有没有看过:panResponder。我正在研究多个 d'n'd 元素,而 panResponder 最适合

【讨论】:

【参考方案4】:

react-beautiful-dnd 可以满足您的需求。

【讨论】:

【参考方案5】:

试试这个它肯定会在你的情况下工作!

react-beautiful-dnd multi drag pattern

https://github.com/atlassian/react-beautiful-dnd/tree/master/stories/src/multi-drag

演示:https://react-beautiful-dnd.netlify.app/?path=/story/multi-drag--pattern

import React,  Component  from 'react';
import styled from '@emotion/styled';
import  DragDropContext  from 'react-beautiful-dnd';
import initial from './data';
import Column from './column';

import type  Result as ReorderResult  from './utils';
import  mutliDragAwareReorder, multiSelectTo as multiSelect  from './utils';

import type  DragStart, DropResult, DraggableLocation  from 'react-beautiful-dnd';
import type  Task, Id  from '../types';
import type  Entities  from './types';

const Container = styled.div`
  display: flex;
  user-select: none;
  justify-content: center;
`;

type State = 
  entities: Entities,
  selectedTaskIds: Id[],
  columnFlag: false,

  // sad times
  draggingTaskId: Id,
;

const getTasks = (entities: Entities, columnId: Id): Task[] =>
  entities.columns[columnId].taskIds.map(
    (taskId: Id): Task => entities.tasks[taskId],
  );

export default class TaskApp extends Component<any, State> 
  state: State = 
    entities: initial,
    selectedTaskIds: [],
    draggingTaskId: '',
  ;

  componentDidMount() 
    window.addEventListener('click', this.onWindowClick);
    window.addEventListener('keydown', this.onWindowKeyDown);
    window.addEventListener('touchend', this.onWindowTouchEnd);
  

  componentWillUnmount() 
    window.removeEventListener('click', this.onWindowClick);
    window.removeEventListener('keydown', this.onWindowKeyDown);
    window.removeEventListener('touchend', this.onWindowTouchEnd);
  

  onDragStart = (start: DragStart) => 
    const id: string = start.draggableId;
    const selected: Id = this.state.selectedTaskIds.find(
      (taskId: Id): boolean => taskId === id,
    );

    // if dragging an item that is not selected - unselect all items
    if (!selected) 
      this.unselectAll();
    
    this.setState(
      draggingTaskId: start.draggableId,
    );
  ;

  onDragEnd = (result: DropResult) => 
    const destination = result.destination;
    const source = result.source;
    const draggableId = result.draggableId;  
    const combine  = result.combine;

    console.log('combine',combine);
    console.log('destination',destination);
    console.log('source',source);
    console.log('draggableId',draggableId);
    
    // nothing to do
    if (!destination || result.reason === 'CANCEL') 
      this.setState(
        draggingTaskId: '',
      );
      return;
    

    const processed: ReorderResult = mutliDragAwareReorder(
      entities: this.state.entities,
      selectedTaskIds: this.state.selectedTaskIds,
      source,
      destination,
    );

    this.setState(
      ...processed,
      draggingTaskId: null,
    );
  ;

  onWindowKeyDown = (event: KeyboardEvent) => 
    if (event.defaultPrevented) 
      return;
    

    if (event.key === 'Escape') 
      this.unselectAll();
    
  ;

  onWindowClick = (event: KeyboardEvent) => 
    if (event.defaultPrevented) 
      return;
    
    this.unselectAll();
  ;

  onWindowTouchEnd = (event: TouchEvent) => 
    if (event.defaultPrevented) 
      return;
    
    this.unselectAll();
  ;

  toggleSelection = (taskId: Id) => 
    const selectedTaskIds: Id[] = this.state.selectedTaskIds;
    const wasSelected: boolean = selectedTaskIds.includes(taskId);
    
    console.log('hwwo',this.state.entities.columns);
    console.log('hwwo',this.state.entities.columns.done.taskIds);

    
    // if there is change in entities - update the state
        

    const newTaskIds: Id[] = (() => 
      // Task was not previously selected
      // now will be the only selected item
      if (!wasSelected) 
        return [taskId];
      

      // Task was part of a selected group
      // will now become the only selected item
      if (selectedTaskIds.length > 1) 
        return [taskId];
      

      // task was previously selected but not in a group
      // we will now clear the selection
      return [];
    )();

    this.setState(
      selectedTaskIds: newTaskIds,
    );
  ;

  toggleSelectionInGroup = (taskId: Id) => 
    const selectedTaskIds: Id[] = this.state.selectedTaskIds;
    const index: number = selectedTaskIds.indexOf(taskId);

    // if not selected - add it to the selected items
    if (index === -1) 
      this.setState(
        selectedTaskIds: [...selectedTaskIds, taskId],
      );
      return;
    

    // it was previously selected and now needs to be removed from the group
    const shallow: Id[] = [...selectedTaskIds];
    shallow.splice(index, 1);
    this.setState(
      selectedTaskIds: shallow,
    );
  ;

  // This behaviour matches the MacOSX finder selection
  multiSelectTo = (newTaskId: Id) => 
    const updated: string[] | null | undefined = multiSelect(
      this.state.entities,
      this.state.selectedTaskIds,
      newTaskId,
    );

    if (updated == null) 
      return;
    

    this.setState(
      selectedTaskIds: updated,
    );
  ;

  unselect = () => 
    this.unselectAll();
  ;

  unselectAll = () => 
    this.setState(
      selectedTaskIds: [],
    );
  ;

  render() 
    const entities = this.state.entities;
    const selected = this.state.selectedTaskIds;

    console.log('entities', entities);
    console.log('selected', selected);
    
    return (
      <DragDropContext
        onDragStart=this.onDragStart
        onDragEnd=this.onDragEnd
      >
        <Container>
          entities.columnOrder.map((columnId: Id) => (
            <Column
              column=entities.columns[columnId]
              tasks=getTasks(entities, columnId)
              selectedTaskIds=selected
              key=columnId
              draggingTaskId=this.state.draggingTaskId
              toggleSelection=this.toggleSelection
              toggleSelectionInGroup=this.toggleSelectionInGroup
              multiSelectTo=this.multiSelectTo
              entities=entities
            />
          ))
        </Container>
      </DragDropContext>
    );
  

【讨论】:

以上是关于如何在 React 中拖放多个元素?的主要内容,如果未能解决你的问题,请参考以下文章

在嵌套数据表中拖放

如何在 Silverlight 中拖放“框”

如何在 JTable 中拖放一行?

SwiftUI:如何在 macOS 上从 Mail 中拖放电子邮件

如何使用 jQuery 在 div 中多次拖放图像以及在 drop div 中拖放图像?

如何在 obj-c 中拖放“.txt”文件