Search code examples
javascriptreactjsscrolladdeventlistener

Unexpected Vertical Scroll Before Horizontal Scrolling in a React Component


I'm developing a React application and encountering an issue with scrolling behavior. Despite setting up conditional scrolling logic based on the component's state, I'm observing an unwanted small vertical scroll before the horizontal scrolling starts. This occurs when transitioning between vertical and horizontal scrolling modes. I really don't understand what it's causing it.

What I want:

enter image description here

What is happening:

enter image description here

My App.tsx where the state is handled.

import "./App.css";
import CurrentLocation from "./components/CurrentLocation";
import Footer from "./components/Footer.js";
import Landing from "./components/Landing";
import { debounce } from "./utils/debounce.js";

function App() {
  const [indexScroll, setIndexScroll] = useState(0);
  const [indexHoriz, setIndexHoriz] = useState(0);

  useEffect(() => {
    console.log("Current vertical index: ", indexScroll);

    // handle vertical scroll
    const handleScrolling = debounce((e: any) => {
      // get main element
      const main = document.querySelector("main");
      e.preventDefault();
      // if scrolling up
      if (e.wheelDelta <= 0) {
        // if we are not at the last div, scroll to next one
        if (indexScroll < main?.children.length!! - 1) {
          console.log("Scrolling up");
          setIndexScroll(indexScroll + 1);
          main?.children[indexScroll + 1].scrollIntoView({
            behavior: "smooth",
          });
        } else {
          // if last div, go back to the first one
          setIndexScroll(0);
          main?.children[0].scrollIntoView({
            behavior: "smooth",
          });
        }
      } else {
        // if scrolling down
        // if we are not in the first div, scroll back
        if (indexScroll > 0) {
          setIndexScroll(indexScroll - 1);
          main?.children[indexScroll - 1].scrollIntoView({
            behavior: "smooth",
          });
        } else {
          // if we are at the first div, do nothing
          console.log("You are at the first div, can't scroll back.");
        }
      }
    }, 50);

    // handle horiz scroll
    const handleScrollingHoriz = debounce((e: any) => {
      // get main element
      const main = document.querySelector("#current-location");
      e.preventDefault();
      // if scrolling up
      if (e.wheelDelta <= 0) {
        // if we are not at the last div, scroll to next one
        if (indexHoriz < main?.children.length!! - 1) {
          console.log("Scrolling up");
          setIndexHoriz(indexHoriz + 1);
          main?.children[indexHoriz + 1].scrollIntoView({
            behavior: "smooth",
          });
        } else {
          // if last div, go back to the first one
          setIndexHoriz(0);
          main?.children[0].scrollIntoView({
            behavior: "smooth",
          });
        }
      } else {
        // if scrolling down
        // if we are not in the first div, scroll back
        if (indexHoriz > 0) {
          setIndexHoriz(indexHoriz - 1);
          main?.children[indexHoriz - 1].scrollIntoView({
            behavior: "smooth",
          });
        } else {
          // if we are at the first div, do nothing
          console.log("You are at the first div, can't scroll back.");
        }
      }
    }, 50);

    // if user is in div n°1 (current location) activate horizontal scrolling
    if (indexScroll === 1) {
      window.removeEventListener("mousewheel", handleScrolling);
      window.addEventListener("mousewheel", handleScrollingHoriz);
    } else {
      window.addEventListener("mousewheel", handleScrolling);
    }
  }, [indexScroll, indexHoriz]);

  return (
    <main>
      <Landing />
      <CurrentLocation />
      <Footer />
    </main>
  );
}

export default App;

My CurrentLocation component where the divs resides.

import useGeo from "../hooks/useGeo";

const CurrentLocation = () => {
  const { geoData, weatherData, geoLoading, weatherLoading } = useGeo();

  if (geoLoading || weatherLoading)
    return (
      <div id="current-location" className="grid">
        <p>Loading data</p>
      </div>
    );

  return (
    <div id="current-location" className="grid">
      <div className="grid-child">
        <h1>
          It looks like you're in {geoData.city}, {geoData.state_prov}.
          <br /> The current weather is{" "}
          {weatherData.list[0].weather[0].description} with a temperature of{" "}
          {weatherData.list[0].main.temp.toFixed(0)}°F.
        </h1>
      </div>
      <div className="grid-child">FORECAST 5 DAYS</div>
      <div className="grid-child">SEARCH LOCATION</div>
    </div>
  );
};

export default CurrentLocation;

My App.css and index.css

.full-page > div {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 90%;
  width: 90%;
}

.full-page {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100dvh;
}

#current-location,
.grid-child {
  overflow-x: hidden;
}

.grid {
  display: grid;
  height: 100dvh;
  align-items: center;
  grid-template-columns: 100vw 100vw 100vw;
}

.grid-child {
  display: flex;
  justify-content: center;
  width: 100vw;
}

body {
  box-sizing: border-box;
  background-color: black;
  color: white;
  overflow: hidden;
  font-family: "DM Sans", sans-serif;
}

h1 {
  padding: 30px;
  font-size: 2.5em;
  font-weight: 400;
}

Solution

  • Found a solution. The .grid-child was resizing on scrolling based on the height of the following div and making it looking like a scroll problem. I added a fixed height of full screen to the class.

    .grid-child {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100vw;
      height: 100dvh;
    }