【问题标题】:How to process mouseMove event in parent in React?如何在 React 中处理父级的 mouseMove 事件?
【发布时间】:2019-11-11 08:35:46
【问题描述】:

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

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

我尝试在父级的context 中设置clientXclientY,然后将其从DragMe 组件中拉入,但由于某种奇怪的原因,第一次总是undefined我给它一个默认值。

这是我正在使用的代码:

    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>
      )
    }

【问题讨论】:

  • 尝试添加 e.preventDefault();在每个事件方法中
  • 也尝试添加e.stopPropagation()
  • 我最初尝试过,但问题不是某处的其他事件。问题是 mousemove 仅在鼠标仍在对象上方时才会发出。当鼠标移动过快时,光标将在其位置更新之前离开对象。

标签: reactjs svg draggable react-hooks mousemove


【解决方案1】:

经过大量实验,我能够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>
      )
    }

【讨论】:

    【解决方案2】:

    只是为了隔离在这里工作的代码: 首先在render() 函数中弄清楚你的顶级父级是什么,你可能有这样的东西:

    render() {
      return (
        <div ref={(parent_div) => { this.parent_div = parent_div }}}> 
          // other div stuff 
        </div>
      )
    }
    
    

    然后,使用上面的 ref 分配,继续为它分配事件侦听器,如下所示:

    this.parent_div.addEventListener('mousemove', function (event) {
      console.log(event.clientX)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多