在 React js 中检测滚动方向

新手上路,请多包涵

我试图检测滚动事件是向上还是向下,但我找不到解决方案。

 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;

基本上它总是被检测为“关闭”,因为 yhandleNavigation 中始终为 0。如果我检查 DevTool 中的状态, y 中的状态更新 handleNavigation 没有。

有什么建议我做错了什么吗?

谢谢你的帮助

原文由 Mugg84 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

TLDR;

由于这个答案引起了一些关注,所以我刚刚基于这个答案开发了一个npm包,让大家在他们的项目中将它作为一个独立的包/库来使用。因此,如果您只想立即使用某些东西,您可以将它添加到您的项目中:

 npm i @smakss/react-scroll-direction
or
yarn add @smakss/react-scroll-direction

或者 在此处阅读更多相关信息

答案及其描述

This is because you defined a useEffect() without any dependencies , so your useEffect() will only run once, and it never calls handleNavigation() on y 变化。要解决此问题,您需要将 y 添加到依赖项数组,以告诉您的 useEffect()y 值更改后运行。然后你需要另一个更改才能在你的代码中生效,你正在尝试用 --- 初始化你的 y window.scrollY ,所以你应该在你的 useState() 中执行此操作 --- 喜欢:

 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]);

如果由于某种原因 window 在那里不可用或者你不想在这里做,你可以在两个单独的 useEffect() 中完成。

所以你的 useEffect() 应该是这样的:

 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]);

更新(工作解决方案)

在我自己实施这个解决方案之后。我发现有一些注释应该应用于此解决方案。 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() ,那么由于这个变化我们应该优化 handleNavigation() ,所以我们应该使用 useCallback() 。那么最后的结果会是这样的:

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

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", handleNavigation);

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

在@RezaSam 发表评论后,我注意到我在记忆版本中犯了一个微小的错误。我在另一个箭头函数中调用 handleNavigation 的地方,我发现(通过浏览器开发工具,事件侦听器选项卡)在每个组件重新渲染中都会将一个新事件注册到 window 所以它可能毁了整个事情。

工作演示:

代码沙箱


最终优化方案

毕竟,我最终认为这种情况下的 记忆 将帮助我们注册单个事件,识别滚动方向,但它在打印控制台时并未完全优化,因为我们在 handleNavigation 函数中进行安慰,并且在当前实现中没有其他方法可以打印所需的控制台。

所以,我意识到每次我们想要检查新位置时,都有一种更好的方法来存储最后一页滚动位置。同样为了摆脱大量的 向上 滚动和向下 滚动的安慰,我们应该定义一个阈值 (使用去抖动方法) 来触发滚动事件的变化。所以我只是在网上搜索了一下,最后得到了这个非常有用的 要点。然后在它的启发下,我实现了一个更简单的版本。

这是它的样子:

 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);
  console.log(scrollDir);

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

这个怎么运作?

我将简单地从上到下解释每个代码块。

  • 所以我刚刚定义了一个初始值为 0 的阈值点,然后每当滚动向上或向下时,它都会进行新的计算,如果你不想立即计算新的页面偏移量,你可以增加它.

  • 然后而不是使用 scrollY 我决定使用 pageYOffset 这在交叉浏览中更可靠。

  • updateScrollDir 函数中,我们将简单地检查是否满足阈值,如果满足,我将根据当前页面和上一页偏移指定滚动方向。

  • 其中最重要的部分是 onScroll 函数。我刚刚使用 requestAnimationFrame 来确保我们在页面滚动后完全呈现后计算新的偏移量。然后使用 ticking 标志,我们将确保我们只在每个 requestAnimationFrame 中运行一次事件监听器回调。

  • 最后,我们定义了监听器和清理函数。

  • 然后 scrollDir 状态将包含实际的滚动方向。

工作演示:

代码沙箱

原文由 SMAKSS 发布,翻译遵循 CC BY-SA 4.0 许可协议

只是想提出一个简洁的解决方案,它与 habbahans 非常相似,但在我看来看起来更简洁一些。

 let oldScrollY = 0;

const [direction, setDirection] = useState('up');

const controlDirection = () => {
    if(window.scrollY > oldScrollY) {
        setDirection('down');
    } else {
        setDirection('up');
    }
    oldScrollY = window.scrollY;
}

useEffect(() => {
    window.addEventListener('scroll', controlDirection);
    return () => {
        window.removeEventListener('scroll', controlDirection);
    };
},[]);

在这里,您只需访问 hidden 状态即可在代码中执行您希望的操作。

原文由 kiwikodes 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题