I have a component that resizes into a horizontal row of bootstrap cards when in a smaller desktop window. For users without a horizontal mouse wheel and not using a touchpad, I would like to allow users to scroll horizontally using their vertical mouse wheel movements when hovering over this particular component.
Here is the original StackOverflow issue I based my code off of: https://stackoverflow.com/a/15343916/8387497
Horizontal Scroll helper component:
function horizontalScroll (event) {
const delta = Math.max(-1, Math.min(1, (event.nativeEvent.wheelDelta || -event.nativeEvent.detail)))
event.currentTarget.scrollLeft -= (delta * 10)
event.preventDefault
}
How I've implemented it on component requiring horizontal scrolling:
<Row className='announcements-home' onWheel={horizontalScroll} >
When I've placed this horizontalScroll
helper function within the React onWheel event, it scrolls horizontally AND vertically. My desired outcome is just horizontal scrolling. Also, Firefox does not appear to respond at all to horizontal scrolling with these changes.
onWheel = (e) => {
e.preventDefault()
var container = document.getElementById('container')
var containerScrollPosition = document.getElementById('container').scrollLeft
container.scrollTo({
top: 0,
left: largeContainerScrollPosition + e.deltaY
behaviour: 'smooth' //if you want smooth scrolling
})
}
Okay, so the issue seems to be that you only refer to the function event.preventDefault
rather than invoking it.
Adding some brackets at the end to invoke it should do the trick:
event.preventDefault()
.
I however found this issue while looking for some simple code to use, so I will also leave the hook I made for this if others in the same situation:
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: "smooth"
});
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
Usage:
import React from "react";
import { useSideScroll } from "./useSideScroll";
export const SideScrollTest = () => {
const scrollRef = useHorizontalScroll();
return (
<div ref={scrollRef} style={{ width: 300, overflow: "auto" }}>
<div style={{ whiteSpace: "nowrap" }}>
I will definitely overflow due to the small width of my parent container
</div>
</div>
);
};
Note: The scroll behavior "smooth" seems to be giving some trouble when trying to do continuous scrolling. This behavior can be omitted to have proper continuous scrolling, but it will look jerky.
As far as I know, there is no easy solution for this. I have however created a rather involved solution in my own project, so thought some people may appreciate that also: https://gist.github.com/TarVK/4cc89772e606e57f268d479605d7aded
There is another small problem with TarVK's proposed hook. Once you scroll to the end and continue scrolling nothing happens, when we are used to containing elements starting to scroll as well. So I made a fix for that:
export function useHorizontalScroll () {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = (e) => {
if (e.deltaY === 0) return;
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: 'smooth'
});
};
el.addEventListener('wheel', onWheel);
return () => el.removeEventListener('wheel', onWheel);
}
}, []);
return elRef;
}
It's conditionally preventing default behavior only when there is space to scroll in that direction, so when there is no space to scroll, for example the whole page will start to scroll. The change is here:
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
I can not comment, because my reputation is not enough.
@arVK's answer works well, but using 'scrollBy' instead of 'scrollTo' can get more smooth wheel.
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollBy(e.deltaY, 0);
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With