Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Horizontal Scrolling on React Component Using Vertical Mouse Wheel

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.

like image 716
beznet Avatar asked May 15 '19 16:05

beznet


4 Answers

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
    })
}
like image 160
khode Avatar answered Oct 19 '22 17:10

khode


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

like image 26
TarVK Avatar answered Oct 19 '22 19:10

TarVK


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();
        }
like image 6
dorsta Avatar answered Oct 19 '22 19:10

dorsta


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;
}
like image 1
Wei Hung Avatar answered Oct 19 '22 18:10

Wei Hung