javascriptreactjssettimeoutgsap

how to know whether we are scrolling the page or not?


How to detect mouse scroll event, how to get a state where we know that we're not currently doing the scrolling. How to create react custom hook out of that situation. the hook is going to produce false state if there's no scroll event going on, and true state for the opposite situation.

I've used setTimeout for this problem before but the result was not quiet what I want. the code looks like this when using setTimeout

  const [isWheeling, setIsWheeling] = useState(false);

  useEffect(() => {
    let scrollEndTimer;
    const wheelTimer = () => {
      setIsWheeling(true)

      clearTimeout(scrollEndTimer)
      scrollEndTimer = setTimeout(() => {
      // setTimeout(() => {
        setIsWheeling(false)
      }, 300)
    }

    window.addEventListener('wheel', wheelTimer)
    return () => window.removeEventListener('wheel', wheelTimer)
  }, [])

  useEffect(() => {
    if (isWheeling) {
      gsap.to(svgHeightRef.current, {
        value: 200,
        duration: 0.5 
      })
    } else {
      gsap.to(svgHeightRef.current, {
        value: 500,
        duration: 1
      })
    }

  }, [isWheeling])

it does not behave the way I want it to, there might be something wrong with the way I implement the setTimeout.

The issue is simply how to detect whether I move my mouse wheel or not. If I do nothing with the mouse wheel, give me false state, and whenever I touch my wheel, it gives me true state right away.

Can anyone help me regarding of this problem. Thank you


Solution

  • import * as React from 'react'
    
    export const useIsScrolling = (target = document) => {
      const [isScrolling, setIsScrolling] = React.useState(false)
      const on = React.useCallback(() => setIsScrolling(true), [])
      const off = React.useCallback(() => setIsScrolling(false), [])
      React.useEffect(() => {
        target.addEventListener('scroll', on, { passive: true })
        target.addEventListener('scrollend', off)
        return () => {
          target?.removeEventListener('scroll', on)
          target?.removeEventListener('scrollend', off)
        }
      }, [])
    
      return isScrolling
    }
    

    Use as

    // document scrolling:
    const isScrolling = useIsSCrolling()
    
    // or, with a specific target element:
    const isScrolling = useIsScrolling(
      document.querySelector('your-selector-here')
    )
    

    Demo:

    const useIsScrolling = (target = document) => {
      const [isScrolling, setIsScrolling] = React.useState(false)
      const on = React.useCallback(() => setIsScrolling(true), [])
      const off = React.useCallback(() => setIsScrolling(false), [])
      React.useEffect(() => {
        target.addEventListener('scroll', on, { passive: true })
        target.addEventListener('scrollend', off)
        return () => {
          target && target.removeEventListener('scroll', on)
          target && target.removeEventListener('scrollend', off)
        }
      }, [])
    
      return isScrolling
    }
    
    const App = () => {
      const isScrolling = useIsScrolling()
      return <pre>{JSON.stringify({ isScrolling }, null, 2)}</pre>
    }
    ReactDOM.createRoot(root).render(<App />)
    #root {
      height: 800vh;
      background: repeating-linear-gradient(
        45deg,
        #fff,
        #fff 30px,
        #f5f5f5 30px,
        #f5f5f5 60px
      );
    }
    pre {
      position: fixed;
      top: 0;
    }
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <div id="root"></div>

    Notes:


    1 - with one exception: when the user scrolls the document, scroll and scrollend are triggered on both document and window.