React Js 配对 2 列拼图

问题描述 投票:0回答:1

我需要为我的项目从 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" },
  ]);

检查图像以便更好地理解。

enter image description here

有什么办法可以做到这一点吗?

reactjs react-native npm npm-package
1个回答
0
投票

您可以使用画布并给它一个参考。

现在,您可以为画布设置动画并绘制点和边缘。

从一个节点单击(并拖动)以将其连接到另一个节点。您可以添加一些逻辑来防止同一列中的项目之间出现边缘。

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>

以下是连接项目的示例:

enter image description here

现在您可以导出节点和边以及侦听器,并检查图形的关系。

© www.soinside.com 2019 - 2024. All rights reserved.