javascript - 在 React js 中检测滚动方向

标签 javascript reactjs scroll event-handling

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

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;
基本上它总是被检测为“down”,因为 yhandleNavigation始终为 0。如果我在 DevTool 中检查状态,y状态更新,但在 handleNavigation没有。
任何建议我做错了什么?
谢谢你的帮助

最佳答案

这是因为您定义了 useEffect()没有任何依赖 , 所以 您的 useEffect()只会运行一次,它永远不会调用 handleNavigation()y变化 .要解决此问题,您需要添加 y到你的依赖数组告诉你的useEffect()运行一次 y值(value)得到改变。然后您需要另一个更改才能在您的代码中生效,您正在尝试初始化您的 ywindow.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]);
如果由于某种原因窗口可能在那里不可用或者您不想在此处执行此操作,您可以在两个单独的 useEffect() 中执行此操作。 s。
所以你的useEffect() s 应该是这样的:
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]);
更新(工作解决方案)
在我自己实现这个解决方案之后。我发现有一些注释应该适用于这个解决方案。所以自从handleNavigation()将改变y直接取值我们可以忽略y作为我们的依赖然后添加handleNavigation()作为我们的 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 注册一个新事件所以它可能会毁了整个事情。
工作演示:
CodeSandbox

最终优化方案
毕竟,我最终得到了 memoization在这种情况下,将帮助我们注册单个事件,以识别滚动方向,但它在打印控制台时并未完全优化,因为我们在 handleNavigation 内部进行安慰功能,并且在当前实现中没有其他方法可以打印所需的控制台。
所以,我意识到每次我们想要检查新位置时,有一种更好的方法来存储最后一页滚动位置。另外为了摆脱大量的向上滚动和向下滚动的安慰,我们应该定义一个阈值(使用去抖动方法)来触发滚动事件的变化。所以我只是在网上搜索了一下,最后得到了这个 gist这非常有用。然后在它的启发下,我实现了一个更简单的版本。
这是它的外观:
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 state 将包含实际的滚动方向。

  • 工作演示:
    CodeSandbox

    关于javascript - 在 React js 中检测滚动方向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62497110/

    相关文章:

    javascript - 主干监听属性更改

    javascript - 我有一个加载嵌入式 YouTube 视频的组件,如下所示

    javascript - 隐藏 Chart.js 中的所有标签和工具提示并使其非常小

    Android 无法去除顶部和底部的颜色

    jquery - 如何使用溢出滚动显示更多/显示更少?

    JavaScript - 使窗口滚动条跟随鼠标

    java - 使用 jquery.parseJSON() 解析 Java JSONArray

    Javascript 日期工作奇怪

    javascript - Js 拆分函数给出错误的结果

    javascript - Next.js - 带有动态路由的浅路由