【问题标题】:Toggle Class based on scroll React JS基于滚动 React JS 的切换类
【发布时间】:2017-11-20 14:17:44
【问题描述】:

我正在使用 bootstrap 4 导航栏,并希望在 ig 400px 向下滚动后更改背景颜色。我正在查看 react docs 并找到了一个 onScroll 但找不到太多信息。到目前为止,我...

我不知道我是否使用了正确的事件监听器或如何设置高度等。

而且我并没有真正设置内联样式...

  import React, { Component } from 'react';

   class App extends Component {

   constructor(props) {
    super(props);

      this.state = {  scrollBackground: 'nav-bg' };
      this.handleScroll = this.handleScroll.bind(this);
   }


   handleScroll(){
      this.setState ({
         scrollBackground: !this.state.scrollBackground
       })
    }

 render() {
 const scrollBg = this.scrollBackground ? 'nav-bg scrolling' : 'nav-bg';

 return (
   <div>

       <Navbar inverse toggleable className={this.state.scrollBackground} 
                                  onScroll={this.handleScroll}>
        ...
      </Navbar>

    </div>
   );
  }
}

export default App;

【问题讨论】:

标签: javascript reactjs twitter-bootstrap events dom-events


【解决方案1】:

添加滚动侦听器的一种方法是使用componentDidMount() 生命周期方法。下面的例子应该给你一个想法:

import React from 'react';
import { render } from 'react-dom';

class App extends React.Component {
  state = {
    isTop: true,
  };

  componentDidMount() {
    document.addEventListener('scroll', () => {
      const isTop = window.scrollY < 100;
      if (isTop !== this.state.isTop) {
          this.setState({ isTop })
      }
    });
  }
  render() {
    return (
      <div style={{ height: '200vh' }}>
        <h2 style={{ position: 'fixed', top: 0 }}>Scroll {this.state.isTop ? 'down' : 'up'}!</h2>
      </div>
    );
  }
} 

render(<App />, document.getElementById('root'));

当您的 scrollY 位置为 100 及以上时,这会将文本从“向下滚动”更改为“向上滚动”。

编辑:应该避免在每次滚动时更新状态的过度杀伤力。仅在布尔值更改时更新它。

【讨论】:

  • 感谢 Glenn,这很有效,谢谢,我将其修改为我的代码,以防有人遇到同样的问题 &lt;Navbar inverse toggleable style={{ top: 0 }} className={this.state.isTop ? 'pt-3 sticky-top nav-bg' : 'pt-3 sticky-top nav-bg scrolling'} &gt;
  • 添加此代码后出现此警告:Warning: setState(...): Can only update a mounted or mounting component.
【解决方案2】:

更好

import React from 'react';
import { render } from 'react-dom';

class App extends React.Component {
    constructor(props) {
    super(props);

    this.state = {
      isTop: true
    };
    this.onScroll = this.onScroll.bind(this);
  }

  componentDidMount() {
    document.addEventListener('scroll', () => {
      const isTop = window.scrollY < 100;
      if (isTop !== this.state.isTop) {
        this.onScroll(isTop);
      }
    });
  }

  onScroll(isTop) {
    this.setState({ isTop });
  }

  render() {
    return (
      <div style={{ height: '200vh' }}>
        <h2 style={{ position: 'fixed', top: 0 }}>Scroll {this.state.isTop ? 'down' : 'up'}!</h2>
      </div>
    );
  }
} 

render(<App />, document.getElementById('root'));

【讨论】:

  • 为什么这样更好?
  • 因为根据 ReactJS 文档,您不应该在 componentDidMount 挂钩中使用 setState 方法。
【解决方案3】:

对于那些在 2020 年之后阅读此问题的人,我采用了 @glennreyes 的答案并使用 React Hooks 重写了它:

  const [scroll, setScroll] = useState(0)

  useEffect(() => {
    document.addEventListener("scroll", () => {
      const scrollCheck = window.scrollY < 100
      if (scrollCheck !== scroll) {
        setScroll(scrollCheck)
      }
    })
  })

请记住,useState 有一个包含两个元素的数组,第一个是 state 对象,第二个是 更新它的函数

useEffect 帮助我们替换 componentDidmount,为了简洁起见,目前编写的函数没有做任何清理工作。

如果你觉得清理很重要,你可以在 useEffect 中返回一个函数。

您可以全面阅读here

更新:

如果你们想让它模块化甚至进行清理,你可以这样做:

  1. 如下创建一个自定义钩子

    import { useState, useEffect } from "react"
    
    export const useScrollHandler = () => {
    // setting initial value to true
    const [scroll, setScroll] = useState(1)
    
    // running on mount
    useEffect(() => {
      const onScroll = () => {
        const scrollCheck = window.scrollY < 10
        if (scrollCheck !== scroll) {
          setScroll(scrollCheck)
        }
      }
    
    // setting the event handler from web API
    document.addEventListener("scroll", onScroll)
    
    // cleaning up from the web API
     return () => {
       document.removeEventListener("scroll", onScroll)
      }
    }, [scroll, setScroll])
    
    return scroll
    
    }
    
  2. 在您认为合适的任何组件中调用它

    const component = () => {
    
    // calling our custom hook
    const scroll = useScrollHandler()
    
    ....... rest of your code
    
    }
    

【讨论】:

  • 清理不应该是document.removeEventListener吗?
  • 我认为您可能需要举一个 ....... rest of your code 的示例。 useScrollHandler 返回什么类型?一个号码?布尔?这不是很明显,然后你如何使用这些数据来使某些东西出现或消失?
  • scrollCheck 是布尔值,滚动是数字
  • @Sam 嘿伙计,感谢您在这里评论类型。
【解决方案4】:

我已经为我的用例更改了@PouyaAtaei 的答案。

import { useState, useEffect } from "react"

// Added distance parameter to determine how much 
// from the top tell return value is updated.
// The name of the hook better reflects intended use.
export const useHasScrolled = (distance = 10) => {

  // setting initial value to false
  const [scroll, setScroll] = useState(false)

  // running on mount
  useEffect(() => {
    const onScroll = () => {
    // Logic is false tell user reaches threshold, then true after.
      const scrollCheck = window.scrollY >= distance;
      if (scrollCheck !== scroll) {
        setScroll(scrollCheck)
      }
    }

    // setting the event handler from web API
    document.addEventListener("scroll", onScroll)

    // cleaning up from the web API
    return () => {
      document.removeEventListener("scroll", onScroll)
    }

  }, [scroll, setScroll])

  return scroll
}

调用钩子:

const component = () => {
  // calling our custom hook and optional distance agument.
  const scroll = useHasScrolled(250)
}

【讨论】:

    【解决方案5】:

    这是我对 hooks 方法的另一种看法,用于滚动显示和隐藏随机页面元素。

    我的灵感来自:Dan Abramov's post here

    您可以在CodeSandbox demo 中查看完整的工作示例。

    下面是useScroll自定义钩子的代码:

    import React, { useState, useEffect } from "react";
    
    export const useScroll = callback => {
      const [scrollDirection, setScrollDirection] = useState(true);
    
      const handleScroll = () => {
        const direction = (() => {
          // if scroll is at top or at bottom return null,
          // so that it would be possible to catch and enforce a special behaviour in such a case.
          if (
            window.pageYOffset === 0 ||
            window.innerHeight + Math.ceil(window.pageYOffset) >=
              document.body.offsetHeight
          )
            return null;
          // otherwise return the direction of the scroll
          return scrollDirection < window.pageYOffset ? "down" : "up";
        })();
    
        callback(direction);
        setScrollDirection(window.pageYOffset);
      };
    
      // adding and cleanning up de event listener
      useEffect(() => {
        window.addEventListener("scroll", handleScroll);
        return () => window.removeEventListener("scroll", handleScroll);
      });
    };
    

    这个钩子会像这样被消耗:

      useScroll(direction => {
        setScrollDirection(direction);
      });
    

    使用此自定义挂钩的完整组件:

    import React, { useState } from "react";
    import ReactDOM from "react-dom";
    import CustomElement, { useScroll } from "./element";
    import Scrollable from "./scrollable";
    
    function Page() {
      const [scrollDirection, setScrollDirection] = useState(null);
    
      useScroll(direction => {
        setScrollDirection(direction);
      });
    
      return (
        <div>
          {/* a custom element that implements some scroll direction behaviour */}
          {/* "./element" exports useScroll hook and <CustomElement> */}
          <CustomElement scrollDirection={scrollDirection} />
          {/* just a lorem ipsum long text */}
          <Scrollable />
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    
    ReactDOM.render(<Page />, rootElement);
    

    最后是 CustomElement 的代码:

    import React, { useState, useEffect } from "react";
    
    export default props => {
      const [elementVisible, setElementVisible] = useState(true);
      const { scrollDirection } = props;
    
      // when scroll direction changes element visibility adapts, but can do anything we want it to do
      // U can use ScrollDirection and implement some page shake effect while scrolling
      useEffect(() => {
        setElementVisible(
          scrollDirection === "down"
            ? false
            : scrollDirection === "up"
            ? true
            : true
        );
      }, [scrollDirection]);
    
      return (
        <div
          style={{
            background: "#ff0",
            padding: "20px",
            position: "fixed",
            width: "100%",
            display: `${elementVisible ? "inherit" : "none"}`
          }}
        >
          element
        </div>
      );
    };
    

    【讨论】:

      【解决方案6】:
       const [scroll, setScroll] = useState(false);
      
       useEffect(() => {
         window.addEventListener("scroll", () => {
           setScroll(window.scrollY > specify_height_you_want_to_change_after_here);
         });
       }, []); 
      

      然后你可以根据滚动改变你的班级或任何东西。

      <nav className={scroll ? "bg-black" : "bg-white"}>...</nav>
      
      

      【讨论】:

      • 感谢它易于使用
      【解决方案7】:

      这是两个钩子 - 一个用于方向(上/下/无),一个用于实际位置

      这样使用:

      useScrollPosition(position => {
          console.log(position)
        })
      
      useScrollDirection(direction => {
          console.log(direction)
        })
      

      这里是钩子:

      import { useState, useEffect } from "react"
      
      export const SCROLL_DIRECTION_DOWN = "SCROLL_DIRECTION_DOWN"
      export const SCROLL_DIRECTION_UP = "SCROLL_DIRECTION_UP"
      export const SCROLL_DIRECTION_NONE = "SCROLL_DIRECTION_NONE"
      
      export const useScrollDirection = callback => {
        const [lastYPosition, setLastYPosition] = useState(window.pageYOffset)
        const [timer, setTimer] = useState(null)
      
        const handleScroll = () => {
          if (timer !== null) {
            clearTimeout(timer)
          }
          setTimer(
            setTimeout(function () {
              callback(SCROLL_DIRECTION_NONE)
            }, 150)
          )
          if (window.pageYOffset === lastYPosition) return SCROLL_DIRECTION_NONE
      
          const direction = (() => {
            return lastYPosition < window.pageYOffset
              ? SCROLL_DIRECTION_DOWN
              : SCROLL_DIRECTION_UP
          })()
      
          callback(direction)
          setLastYPosition(window.pageYOffset)
        }
      
        useEffect(() => {
          window.addEventListener("scroll", handleScroll)
          return () => window.removeEventListener("scroll", handleScroll)
        })
      }
      
      export const useScrollPosition = callback => {
        const handleScroll = () => {
          callback(window.pageYOffset)
        }
      
        useEffect(() => {
          window.addEventListener("scroll", handleScroll)
          return () => window.removeEventListener("scroll", handleScroll)
        })
      }
      

      【讨论】:

        【解决方案8】:

        如何解决:

        警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要解决此问题,请在 useEffect 清理函数中取消所有订阅和异步任务。 菜单新闻

        const [scroll, setScroll] = useState(false);
        
         useEffect(() => {
           window.addEventListener("scroll", () => {
             setScroll(window.scrollY > specify_height_you_want_to_change_after_here);
           });
         }, []); 
        

        【讨论】:

          【解决方案9】:

          没有滚动事件监听器的方法

          import { useEffect, useState } from "react";
          
          interface Props {
            elementId: string;
            position: string;
          }
          
          const useCheckScrollPosition = ({ elementId, position }: Props) => {
            const [isOverScrollPosition, setIsOverScrollPosition] = useState<boolean>(false);
          
            useEffect(() => {
              if (
                "IntersectionObserver" in window &&
                "IntersectionObserverEntry" in window &&
                "intersectionRatio" in window.IntersectionObserverEntry.prototype
              ) {
                const observer = new IntersectionObserver((entries) => {
                  setIsOverScrollPosition(entries[0].boundingClientRect.y < 0);
                });
                const flagElement = document.createElement("div");
                flagElement.id = elementId;
                flagElement.className = "scroll-flag";
                flagElement.style.top = position;
          
                const container = document.getElementById("__next"); // React div id
                const oldFlagElement = document.getElementById(elementId);
          
                if (!oldFlagElement) container?.appendChild(flagElement);
          
                const elementToObserve = oldFlagElement || flagElement;
                observer.observe(elementToObserve);
              }
            }, [elementId, position]);
          
            return isOverScrollPosition;
          };
          
          export default useCheckScrollPosition;
          

          然后你可以像这样使用它:

          const isOverScrollPosition = useCheckScrollPosition({
              elementId: "sticky-header",
              position: "10px",
            });
          

          isOverScrollPosition 是一个布尔值,如果您滚动到提供的位置值 (10px) 上将为真,如果您在其下方滚动则为假。

          这种方法会在 react root 中添加一个标志 div。

          参考:https://css-tricks.com/styling-based-on-scroll-position/

          【讨论】:

            猜你喜欢
            • 2016-04-21
            • 2019-06-11
            • 2012-12-12
            • 1970-01-01
            • 2020-10-17
            • 1970-01-01
            • 1970-01-01
            • 2019-05-01
            • 1970-01-01
            相关资源
            最近更新 更多