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
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:
scrollend
event on either document
or element
.target
is no longer present in DOM when your component is unmounted.scroll
listeners can cause performance issues. It is not the case here, as I used a passive scroll listener.scroll
event does not bubble 1. It is important you get the target
right for this to work (e.g: if the scrolling happens in some app container and you bind on document
or window
it won't work as expected).wheel
based scrolling (keyboard based, touch based, other pointer scrolling methods and programmatic scrolling), with two conditions:
scroll
event never fires, even if the pointer/keyboard/touch event is performed).preventDefault()
is not called on the triggering event (e.g: wheel
, touchstart
, etc)1 - with one exception: when the user scrolls the document
, scroll
and scrollend
are triggered on both document
and window
.