拖放
在这些示例中,我们使用自定义侧边栏组件,在从侧边栏拖动到 React Flow 窗格时创建新节点。
¥In these examples, we use a custom Sidebar component to create new nodes when dragging from the sidebar into the React Flow pane.
拖放式用户界面对于基于节点的工作流编辑器来说非常常见。React Flow 窗格之外的拖放行为不是内置的,但可以使用原生 HTML 拖放 API 、指针事件 或 react-draggable 等第三方库实现。
¥A drag and drop user interface is very common for node-based workflow editors. The drag and drop behavior outside of the React Flow pane is not built in but can be implemented with the native HTML Drag and Drop API , Pointer Events , or a third party library like react-draggable .
使用 HTML 拖放 API 进行拖放
¥Drag and Drop with HTML Drag and Drop API
HTML 拖放 API 是所有主流浏览器都支持的原生 API。这是一个简单的 API,允许你在页面上的不同元素之间拖放元素。
¥The HTML Drag and Drop API is a native API that is supported by all major browsers. It is a simple API that allows you to drag and drop elements between different elements on the page.
注意:HTML 拖放 API 在触摸设备上无法正常支持。请参阅下面的示例,了解使用指针事件的替代方法。
¥Note: HTML Drag and Drop API is not properly supported on touch devices. See the example below for an alternative approach using pointer events.
import React, { useRef, useCallback } from 'react';
import {
ReactFlow,
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
useReactFlow,
Background,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import Sidebar from './Sidebar';
import { DnDProvider, useDnD } from './DnDContext';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
const DnDFlow = () => {
const reactFlowWrapper = useRef(null);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const { screenToFlowPosition } = useReactFlow();
const [type] = useDnD();
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback(
(event) => {
event.preventDefault();
// check if the dropped element is valid
if (!type) {
return;
}
// project was renamed to screenToFlowPosition
// and you don't need to subtract the reactFlowBounds.left/top anymore
// details: https://reactflow.dev/whats-new/2023-11-10
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
},
[screenToFlowPosition, type],
);
const onDragStart = (event, nodeType) => {
setType(nodeType);
event.dataTransfer.setData('text/plain', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className="dndflow">
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDrop={onDrop}
onDragStart={onDragStart}
onDragOver={onDragOver}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
};
export default () => (
<ReactFlowProvider>
<DnDProvider>
<DnDFlow />
</DnDProvider>
</ReactFlowProvider>
);使用指针事件进行拖放
¥Drag and Drop with Pointer Events
另一种实现拖放的方法是使用指针事件。
¥Another way to implement drag and drop is to use pointer events.
指针事件 API 是所有主流浏览器都支持的现代 API。它的实现比 HTML 拖放 API 稍微复杂一些,但通过使用指针事件,我们可以确保拖放行为在所有设备(鼠标和触摸)上保持一致。
¥The Pointer Events API is a modern API that is supported by all major browsers. It is slightly more complex to implement than the HTML Drag and Drop API, but by using pointer events we can make sure that the drag and drop behavior is consistent across all devices, both mouse and touch.
import { useCallback } from 'react';
import {
Background,
Connection,
Controls,
ReactFlow,
ReactFlowProvider,
addEdge,
useEdgesState,
useNodesState,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Sidebar } from './Sidebar';
import { DnDProvider } from './useDnD';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
function DnDFlow() {
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<div className="dndflow">
<div className="reactflow-wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
}
export default () => (
<ReactFlowProvider>
<DnDProvider>
<DnDFlow />
</DnDProvider>
</ReactFlowProvider>
);使用 Neodrag 进行拖放
¥Drag and Drop with Neodrag
Neodrag 是一个提供简单易用的拖放 API 的库。在实际应用中,你可能希望使用 Neodrag 库以跨平台兼容的方式处理拖放行为,兼容鼠标和触摸设备。
¥Neodrag is a library that provides a simple and easy to use drag and drop API. In a real-world application, you may want to use the Neodrag library to handle the drag and drop behavior in a cross-platform compatible way, compatible with both mouse and touch devices.
import { useCallback } from 'react';
import {
Background,
Connection,
Controls,
ReactFlow,
ReactFlowProvider,
addEdge,
useEdgesState,
useNodesState,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Sidebar } from './Sidebar';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
function DnDFlow() {
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<div className="dndflow">
<div className="reactflow-wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
}
export default () => (
<ReactFlowProvider>
<DnDFlow />
</ReactFlowProvider>
);