在 React js 中检测滚动方向 [英] Detect scroll direction in React js

查看:40
本文介绍了在 React js 中检测滚动方向的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

import React, { useState, useEffect } from "react";从react-router-dom"导入{链接};const Navbar = ({ className }) =>{const [y, setY] = useState(0);const handleNavigation = (e) =>{const window = e.currentTarget;如果 (y > window.scrollY) {console.log(向上滚动");} else if (y < window.scrollY) {console.log(向下滚动");}setY(window.scrollY);};useEffect(() => {setY(window.scrollY);window.addEventListener(scroll", (e) => handleNavigation(e));}, []);返回 (<导航类名称={类名称}><p><i className="fas fa-pizza-slice"></i>食物查找器</p><ul><li><链接到=/">主页</链接><li><Link to="/about">关于</Link></nav>);};导出默认导航栏;

基本上它总是被检测为down";因为 handleNavigation 中的 y 始终为 0.如果我检查 DevTool 中的状态,y 状态会更新,但在 handleNavigation 中> 没有.

任何建议我做错了什么?

感谢您的帮助

解决方案

这是因为你定义了一个useEffect()而没有任何依赖,所以你的useEffect() 只会运行一次,并且不会在 y 更改 时调用 handleNavigation().要解决此问题,您需要将 y 添加到您的依赖项数组中,以告诉您的 useEffect()y 值发生更改后运行.然后你需要另一个改变在你的代码中生效,你试图用 window.scrollY 初始化你的 y,所以你应该在你的 中执行此操作>useState() 喜欢:

const [y, setY] = useState(window.scrollY);useEffect(() => {window.addEventListener(scroll", (e) => handleNavigation(e));返回 () =>{//返回一个清理函数来注销我们的函数,因为它会运行多次window.removeEventListener(scroll", (e) => handleNavigation(e));};}, [y]);

如果由于某种原因窗口可能在那里不可用或者您不想在此处执行此操作,您可以在两个单独的 useEffect() 中执行此操作.

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

useEffect(() => {setY(window.scrollY);}, []);useEffect(() => {window.addEventListener(scroll", (e) => handleNavigation(e));返回 () =>{//返回一个清理函数来注销我们的函数,因为它会运行多次window.removeEventListener(scroll", (e) => handleNavigation(e));};}, [y]);

更新(工作解决方案)

在我自己实施这个解决方案之后.我发现有一些注意事项应该应用于此解决方案.所以由于handleNavigation()会直接改变y的值,我们可以忽略y作为我们的依赖,然后添加handleNavigation() 作为我们useEffect()的依赖,那么由于这个变化我们应该优化handleNavigation(),所以我们应该使用<一个 href="https://reactjs.org/docs/hooks-reference.html#usecallback" rel="noreferrer">useCallback() 用于它.那么最终的结果将是这样的:

const [y, setY] = useState(window.scrollY);const handleNavigation = useCallback(e =>{const window = e.currentTarget;如果 (y > window.scrollY) {console.log(向上滚动");} else if (y < window.scrollY) {console.log(向下滚动");}setY(window.scrollY);}, [y]);useEffect(() => {setY(window.scrollY);window.addEventListener(scroll", handleNavigation);返回 () =>{window.removeEventListener(scroll", handleNavigation);};}, [handleNavigation]);

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

工作演示:


最终优化方案

毕竟,我最终认为

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

解决方案

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 (Working solutions)

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

After a comment from @RezaSam I noticed that I made a teeny tiny mistake in the memoized version. Where I call handleNavigation within another arrow function, I found out (via the browser dev tool, event listeners tab) in each component rerender it will register a new event to the window so it might ruin the whole thing up.

Working demo:


Final Optimized Solution

After all, I ended up that memoization in this case will help us to register a single event, to recognize scroll direction but it is not fully optimized in printing the consoles, because we are consoling inside the handleNavigation function and there is no other way around to print the desired consoles in the current implementation.

So, I realized there is 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 (Use debounce approach) 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);
  console.log(scrollDir);

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

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 is 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 based on the current and previous page offset.

  • The most important part of it is the onScroll function. I 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:

这篇关于在 React js 中检测滚动方向的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆