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

这是我在 StackOverflow 上的第一个问题。我想用 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 多个元素的近似解决方案是什么。

感谢您的宝贵时间!

【问题讨论】:

    标签: javascript reactjs drag-and-drop


    【解决方案1】:

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

    【讨论】:

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

    如果有人在 2020 年寻找解决方案。这是我的 current solutionreact-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>
            );
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2022-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多