sandiiarov / use-events

🍭 Event hooks
https://sandiiarov.github.io/use-events
492 stars 16 forks source link

useMousePosition does not consider onscroll #177

Open tommedema opened 5 years ago

tommedema commented 5 years ago

Since useMousePosition returns the mouse position in the document (and not the window), the position changes as the user scrolls. Yet this is not recognized by the hook. See this video:

https://www.loom.com/share/7009214d57ba44d8b6bc2e0bbdbf04e1

useMousePosition should therefore update the X,Y when onscroll is fired. I also think it would be good to include an option to only look at the mouse position in the window, and not the document, as that is often more useful.

tommedema commented 5 years ago

I've come up with this solution for now. Can you think of a case where this logic would fail?

useMousePosition.ts

/**
 * Inspired by: https://github.com/sandiiarov/use-events/blob/master/src/useMousePosition/index.tsx
 * 
 * Enhanced to take scrolling into consideration.
 * @see https://github.com/sandiiarov/use-events/issues/177
 */

import React, { useEffect, useState } from 'react'

const useMousePosition = (): [
  number,
  number,
  { onMouseMove: (e: React.MouseEvent) => void }
] => {
  const [lastScrollX, setLastScrollX] = useState(
    (window.document.scrollingElement && window.document.scrollingElement.scrollLeft) || 0
  )
  const [lastScrollY, setLastScrollY] = useState(
    (window.document.scrollingElement && window.document.scrollingElement.scrollTop) || 0
  )
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  useEffect(() => {
    const onScroll = () => {
      const scrollElement = window.document.scrollingElement

      if (scrollElement) {
        const {
          scrollTop,
          scrollLeft
        } = scrollElement

        setX(x + scrollLeft - lastScrollX)
        setY(y + scrollTop - lastScrollY)

        setLastScrollX(scrollLeft)
        setLastScrollY(scrollTop)
      }
    }

    window.document.addEventListener('scroll', onScroll, true)

    return () => {
      window.document.removeEventListener('scroll', onScroll, true)
    }
  })

  const bindings = {
    onMouseMove: ({ nativeEvent: { offsetX, offsetY } }: React.MouseEvent) => {
      setX(offsetX)
      setY(offsetY)
    }
  }

  return [x, y, bindings]
}

export default useMousePosition