This was suprisingly complicated because of how React handles passive events. I had to bring to this three different hooks: useState
, useEffect
and useRef
. Let's see how it plays out
const scroll = useRef(null)
const [filter, setFilter] = useState("")
useEffect(()=> {
const element = scroll.current
element.addEventListener("wheel", <handler>)
return ()=> element.removeEventListener("wheel", <handler>)
})
We have to use both useRef
and useEffect
for this to work because of the passive listener behavoiur thingy (check out the details here). That way the wheel event will be handled properly
<img
src={<image>}
ref={scroll}
style={{ filter: `grayscale(${filter}%)` }}
/>
So we've created an image and we've linked it to the ref element created with the useRef
hook, also, we are using the filter
value from the state to set a grayscale filter (which is what we've aimed for). Now, all that's left is dealing with the event listener handler. I've separated it because that way it's much easier to change to do other cool things when scrolling through an element.
const handleScroll = event=> {
event.preventDefault()
const direction_y = event.deltaY !== 0 ? event.deltaY > 0 ? "forward" : "backwards" : "none"
requestAnimationFrame(()=> {
const current_grayscale = Math.min(Math.max(1, filter), 99)
if (direction_y === "forward") setFilter(current_grayscale + 2)
if (direction_y === "backwards") setFilter(current_grayscale - 2)
})
}
Thanks to the scroll's deltaY
it's easy to tell what the scroll is doing, and increase or decrease the filter accordingly. A bit of clamping to avoid going overboard, and that's pretty much it.