如何在 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 中拖放多个元素?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:如何在 macOS 上从 Mail 中拖放电子邮件