Detect scroll direction in React js

时光毁灭记忆、已成空白 提交于 2020-06-28 06:42:36

问题


I'm trying to detect if the scroll event is up or down but I can't find the solution.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

const Navbar = ({ className }) => {
  const [y, setY] = useState(0);

  const handleNavigation = (e) => {
    const window = e.currentTarget;
    if (y > window.scrollY) {
      console.log("scrolling up");
    } else if (y < window.scrollY) {
      console.log("scrolling down");
    }
    setY(window.scrollY);
  };

  useEffect(() => {
    setY(window.scrollY);

    window.addEventListener("scroll", (e) => handleNavigation(e));
  }, []);

  return (
    <nav className={className}>
      <p>
        <i className="fas fa-pizza-slice"></i>Food finder
      </p>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
    </nav>
  );
};

export default Navbar;

Basically it's always detected as "down" because y in handleNavigation is always 0. If i check the state in DevTool the y state updates but in the handleNavigation doesn't.

Any suggestions what am I doing wrong?

Thanks for your help


回答1:


This is because you defined a useEffect() without any dependencies, so your useEffect() will only run once, and it never calls handleNavigation() on y changes. To fix this you need to add y to your dependency array to tell your useEffect() run once the y value gets changes. Then you need another change to take effect in your code, where you are trying to initialize your y with window.scrollY, so you should either, do this in your useState() like:

const [y, setY] = useState(window.scrollY);

useEffect(() => {
  window.addEventListener("scroll", (e) => handleNavigation(e));

  return () => { // return a cleanup function to unregister our function since its gonna run multiple times
    window.removeEventListener("scroll", (e) => handleNavigation(e));
  };
}, [y]);

If for some reason window may not be available there or you don't want to do it here, you can do it in two separate useEffect()s.

So your useEffect()s should be like this:

useEffect(() => {
  setY(window.scrollY);
}, []);

useEffect(() => {
  window.addEventListener("scroll", (e) => handleNavigation(e));

  return () => { // return a cleanup function to unregister our function since its gonna run multiple times
    window.removeEventListener("scroll", (e) => handleNavigation(e));
  };
}, [y]);

UPDATE

After implementing this solution on my own. I found out there are some notes that should be applied to this solution. So since the handleNavigation() will change y value directly we can ignore the y as our dependency and then add handleNavigation() as a dependency to our useEffect(), then due to this change we should optimize handleNavigation(), so we should use useCallback() for it. Then the final result will be something like this:

const handleNavigation = useCallback(
  e => {
    const window = e.currentTarget;
    if (y > window.scrollY) {
      console.log("scrolling up");
    } else if (y < window.scrollY) {
      console.log("scrolling down");
    }
    setY(window.scrollY);
  }, [y]
);

useEffect(() => {
  setY(window.scrollY);
  window.addEventListener("scroll", e => handleNavigation(e));

  return () => {
    window.removeEventListener("scroll", e => handleNavigation(e));
  };
}, [handleNavigation]);

I test this again but still faced the same problem that we have earlier, and when you try to scroll up it will break and will console some scrolling down within the scroll up event.

So I ended up that memoization in this case will not help us at all. At last, I found out the approach that we using here is not work very well because we should consider a better way of storing the last page scroll position each time we want to check for a new position. Also to get rid of a huge amount of consoling scrolling up and scrolling down, we should define a threshold to trigger the scroll event change. So I just searched through the web a bit and ended up with this gist which was very useful. Then with the inspiration of it, I implement a simpler version.

This is how it looks:

const [scrollDir, setScrollDir] = useState("scrolling down");

useEffect(() => {
  const threshold = 0;
  let lastScrollY = window.pageYOffset;
  let ticking = false;

  const updateScrollDir = () => {
    const scrollY = window.pageYOffset;

    if (Math.abs(scrollY - lastScrollY) < threshold) {
      ticking = false;
      return;
    }
    setScrollDir(scrollY > lastScrollY ? "scrolling down" : "scrolling up");
    lastScrollY = scrollY > 0 ? scrollY : 0;
    ticking = false;
  };

  const onScroll = () => {
    if (!ticking) {
      window.requestAnimationFrame(updateScrollDir);
      ticking = true;
    }
  };

  window.addEventListener("scroll", onScroll);

  return () => window.removeEventListener("scroll", onScroll);
}, []);

How it works?

I will simply go from top to down and explain each block of code.

  • So I just defined a threshold point with the initial value of 0 then whenever the scroll goes up or down it will make the new calculation you can increase it if you don't want to immediately calculate new page offset.

  • Then instead of using scrollY I decide to use pageYOffset which more reliable in cross browsing.

  • In the updateScrollDir function, we will simply check if the threshold is met or not, then if it met I will specify the scroll direction base of the current and previous page offset.

  • The most important part of it is the onScroll function. We just used requestAnimationFrame to make sure that we are calculating the new offset after the page got rendered completely after scroll. And then with ticking flag, we will make sure we are just run our event listener callback once in each requestAnimationFrame.

  • At last, we defined our listener and our cleanup function.

  • Then the scrollDir state will contain the actual scroll direction.

Working demo:



来源:https://stackoverflow.com/questions/62497110/detect-scroll-direction-in-react-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!