Как обработать событие mouseMove у родителя в React? - PullRequest
0 голосов
/ 30 июня 2019

Я пытаюсь реализовать перетаскивание в React и использовать элементы SVG. Проблема в том, что mouseMove не срабатывает, если пользователь слишком быстро перемещает мышь. Это в основном теряет перетаскивание довольно часто. Чтобы решить эту проблему, я думаю, что мне нужно обработать mouseMove в родительском элементе, но не уверен, как это сделать с помощью React. Я пробовал несколько разных подходов безрезультатно.

Я пытался addEventListener('mousemove', ...) на родительском элементе, используя ссылку, но проблема в том, что clientX - это система координат, отличная от текущего компонента. Кроме того, обработчик событий не имеет доступа ни к какому состоянию из компонента (событие с функциями стрелки). Он сохраняет устаревшую ссылку на любое состояние.

Я попытался установить clientX и clientY в context на родительском элементе, а затем вытянуть его из компонента 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>
      )
    }

1 Ответ

0 голосов
/ 30 июня 2019

После долгих экспериментов я смог 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>
      )
    }
...