我需要为我的项目从 2 个垂直列表构建匹配/链接对。
例如配对列表可能是这样的:
const [pairs, setPairs] = useState([
{ id: 1, left: "Apple", right: "Red" },
{ id: 2, left: "Banana", right: "Yellow" },
{ id: 3, left: "Grapes", right: "Purple" },
{ id: 4, left: "Orange", right: "Orange" },
]);
检查图像以便更好地理解。
有什么办法可以做到这一点吗?
您可以使用画布并给它一个参考。
现在,您可以为画布设置动画并绘制点和边缘。
从一个节点单击(并拖动)以将其连接到另一个节点。您可以添加一些逻辑来防止同一列中的项目之间出现边缘。
const { useEffect, useMemo, useRef, useState } = React;
const defaultPairs = [
{ id: 1, left: "Apple", right: "Red" },
{ id: 2, left: "Banana", right: "Yellow" },
{ id: 3, left: "Grapes", right: "Purple" },
{ id: 4, left: "Orange", right: "Orange" },
];
const findClosestNode = (nodes, mouse, threshold = 4) =>
nodes.find(node =>
Math.abs(mouse.x - node.x) < threshold &&
Math.abs(mouse.y - node.y) < threshold);
const renderCanvas = (canvas, leftArr, rightArr) => {
const ctx = canvas.getContext('2d');
ctx.canvas.width = 100;
const { height, width } = ctx.canvas;
const rowHeight = height / leftArr.length;
const nodes = [
...leftArr.map((_, i) => ({ x: 10, y: i * rowHeight + rowHeight / 2 })),
...rightArr.map((_, i) => ({ x: width - 10, y: i * rowHeight + rowHeight / 2 })),
];
const edges = [];
let activeNode = null;
let mouse = null;
let isDragging = false;
const onMouseDown = (e) => {
const closestNode = findClosestNode(nodes, mouse);
activeNode = closestNode !== activeNode ? closestNode : null;
isDragging = true;
};
const onMouseMove = (e) => {
const rect = e.target.getBoundingClientRect();
mouse = { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
const onMouseUp = (e) => {
isDragging = false;
const closestNode = findClosestNode(nodes, mouse);
if (activeNode !== closestNode && closestNode) {
edges.push([activeNode, closestNode]);
activeNode = null;
}
};
const equalsNode = (n1, n2) => n1 && n2 && n1.x === n2.x && n1.y === n2.y;
const animate = (() => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
edges.forEach(([start, end]) => {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
});
nodes.forEach((node) => {
ctx.beginPath();
ctx.arc(node.x, node.y, 4, 0, Math.PI * 2);
ctx.fillStyle = equalsNode(node, activeNode) ? 'black' : 'darkgrey';
ctx.fill();
});
if (isDragging && activeNode) {
const closestNode = findClosestNode(nodes, mouse);
const intent = closestNode || mouse;
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.setLineDash([3, 3]);
ctx.moveTo(activeNode.x, activeNode.y);
ctx.lineTo(intent.x, intent.y);
ctx.stroke();
ctx.setLineDash([]);
}
requestAnimationFrame(animate);
});
animate();
return { onMouseDown, onMouseMove, onMouseUp };
};
const randomize = () => Math.random() - 0.5;
const App = () => {
const canvasRef = useRef();
const [pairs, setPairs] = useState(defaultPairs);
const leftArr = useMemo(
() => pairs.map((pair) => pair.left).sort(randomize),
[pairs]
);
const rightArr = useMemo(
() => pairs.map((pair) => pair.right).sort(randomize),
[pairs]
);
useEffect(() => {
const { onMouseDown, onMouseMove, onMouseUp } = renderCanvas(canvasRef.current, leftArr, rightArr);
// Add listeners
canvasRef.current.addEventListener('mousedown', onMouseDown);
canvasRef.current.addEventListener('mousemove', onMouseMove);
canvasRef.current.addEventListener('mouseup', onMouseUp);
// Cleanup listeners
return () => {
canvasRef.current.removeEventListener('mousedown', onMouseDown);
canvasRef.current.removeEventListener('mousemove', onMouseMove);
canvasRef.current.removeEventListener('mouseup', onMouseUp);
};
}, [canvasRef, leftArr, rightArr]);
return (
<div className="App">
<div className="Col">
{leftArr.map((e) => (
<div key={e}>
{e}
</div>
))}
</div>
<canvas ref={canvasRef} className="Lines"></canvas>
<div className="Col">
{rightArr.map((e) => (
<div key={e}>
{e}
</div>
))}
</div>
</div>
);
};
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.App {
display: grid;
grid-template-columns: auto auto auto;
flex: 1;
height: 80%;
}
.Col {
display: flex;
flex-direction: column;
justify-content: space-around;
flex: 1;
}
.Col:first-child {
align-items: flex-end;
}
.Col:last-child {
align-items: flex-start;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
以下是连接项目的示例:
现在您可以导出节点和边以及侦听器,并检查图形的关系。