首页 > 解决方案 > 如何在 React 中处理父级的 mouseMove 事件?

问题描述

我正在尝试在 React 中实现拖放并使用 SVG 元素。mouseMove如果用户移动鼠标太快,问题不会被触发。它基本上经常失去拖动。为了解决这个问题,我认为我需要处理mouseMove父级中的问题,但不确定如何使用 React 执行此操作。我尝试了几种不同的方法都无济于事。

我尝试addEventListener('mousemove', ...)使用 ref 在父级上,但问题是它clientX与当前组件的坐标系不同。此外,事件处理程序无权访问组件的任何状态(带有箭头函数的事件)。它维护对任何状态的过时引用。

我尝试在父级上设置and clientX,然后将其从组件中拉入,但由于某些奇怪的原因,它总是第一次出现,即使我给它一个默认值。clientYcontextDragMeundefined

这是我正在使用的代码:

    const DragMe = ({ x = 50, y = 50, r = 10 }) => {
      const [dragging, setDragging] = useState(false)
      const [coord, setCoord] = useState({ x, y })
      const [offset, setOffset] = useState({ x: 0, y: 0 })
      const [origin, setOrigin] = useState({ x: 0, y: 0 })

      const xPos = coord.x + offset.x
      const yPos = coord.y + offset.y

      const transform = `translate(${xPos}, ${yPos})`

      const fill = dragging ? 'red' : 'green'
      const stroke = 'black'

      const handleMouseDown = e => {
        setDragging(true)
        setOrigin({ x: e.clientX, y: e.clientY })
      }

      const handleMouseMove = e => {
        if (!dragging) { return }
        setOffset({
          x: e.clientX - origin.x,
          y: e.clientY - origin.y,
        })
      }

      const handleMouseUp = e => {
        setDragging(false)
        setCoord({ x: xPos, y: yPos })
        setOrigin({ x: 0, y: 0 })
        setOffset({ x: 0, y: 0 })
      }

      return (
        <svg style={{ userSelect: 'none' }}
          onMouseDown={handleMouseDown}
          onMouseUp={handleMouseUp}
          onMouseMove={handleMouseMove}
          onMouseLeave={handleMouseUp}
        >
          <circle transform={transform} cx="0" cy="0" r={r} fill={fill} stroke={stroke} />
        </svg>
      )
    }

标签: reactjssvgdraggablereact-hooksmousemove

解决方案


经过大量实验,我能够到addEventListener父画布。我发现我需要这样做useRef才能让mousemove处理程序看到当前状态。我之前遇到的问题是handleParentMouseMove处理程序对状态的引用过时并且从未见过startDragPos.

这是我想出的解决方案。如果有人知道清理它的方法,将不胜感激。

    const DragMe = ({ x = 50, y = 50, r = 10, stroke = 'black' }) => {
      // `mousemove` will not generate events if the user moves the mouse too fast
      // because the `mousemove` only gets sent when the mouse is still over the object.
      // To work around this issue, we `addEventListener` to the parent canvas.
      const canvasRef = useContext(CanvasContext)

      const [dragging, setDragging] = useState(false)

      // Original position independent of any dragging.  Updated when done dragging.
      const [originalCoord, setOriginalCoord] = useState({ x, y })

      // The distance the mouse has moved since `mousedown`.
      const [delta, setDelta] = useState({ x: 0, y: 0 })

      // Store startDragPos in a `ref` so handlers always have the latest value.
      const startDragPos = useRef({ x: 0, y: 0 })

      // The current object position is the original starting position + the distance
      // the mouse has moved since the start of the drag.
      const xPos = originalCoord.x + delta.x
      const yPos = originalCoord.y + delta.y
      const transform = `translate(${xPos}, ${yPos})`

      // `useCallback` is needed because `removeEventListener`` requires the handler
      // to be the same as `addEventListener`.  Without `useCallback` React will
      // create a new handler each render.
      const handleParentMouseMove = useCallback(e => {
        setDelta({
          x: e.clientX - startDragPos.current.x,
          y: e.clientY - startDragPos.current.y,
        })
      }, [])

      const handleMouseDown = e => {
        setDragging(true)
        startDragPos.current = { x: e.clientX, y: e.clientY }
        canvasRef.current.addEventListener('mousemove', handleParentMouseMove)
      }

      const handleMouseUp = e => {
        setDragging(false)
        setOriginalCoord({ x: xPos, y: yPos })
        startDragPos.current = { x: 0, y: 0 }
        setDelta({ x: 0, y: 0 })
        canvasRef.current.removeEventListener('mousemove', handleParentMouseMove)
      }

      const fill = dragging ? 'red' : 'green'

      return (
        <svg style={{ userSelect: 'none' }}
          onMouseDown={handleMouseDown}
          onMouseUp={handleMouseUp}
        >
          <circle transform={transform} cx="0" cy="0" r={r} fill={fill} stroke={stroke} />
        </svg>
      )
    }

推荐阅读