Javascript 在 React.js 中更新组件 onScroll 的样式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29725828/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 03:52:06  来源:igfitidea点击:

Update style of a component onScroll in React.js

javascriptreactjs

提问by Alejandro Pérez

I have built a component in React which is supposed to update its own style on window scroll to create a parallax effect.

我在 React 中构建了一个组件,它应该在窗口滚动时更新自己的样式以创建视差效果。

The component rendermethod looks like this:

组件render方法如下所示:

  function() {
    let style = { transform: 'translateY(0px)' };

    window.addEventListener('scroll', (event) => {
      let scrollTop = event.srcElement.body.scrollTop,
          itemTranslate = Math.min(0, scrollTop/3 - 60);

      style.transform = 'translateY(' + itemTranslate + 'px)');
    });

    return (
      <div style={style}></div>
    );
  }

This doesn't work because React doesn't know that the component has changed, and therefore the component is not rerendered.

这不起作用,因为 React 不知道组件已更改,因此不会重新渲染组件。

I've tried storing the value of itemTranslatein the state of the component, and calling setStatein the scroll callback. However, this makes scrolling unusable as this is terribly slow.

我尝试将 的值存储在itemTranslate组件的状态中,并setState在滚动回调中调用。然而,这使得滚动无法使用,因为这非常慢。

Any suggestion on how to do this?

关于如何做到这一点的任何建议?

回答by Austin Greco

You should bind the listener in componentDidMount, that way it's only created once. You should be able to store the style in state, the listener was probably the cause of performance issues.

您应该在 中绑定侦听器componentDidMount,这样它只会创建一次。您应该能够将样式存储在状态中,侦听器可能是性能问题的原因。

Something like this:

像这样的东西:

componentDidMount: function() {
    window.addEventListener('scroll', this.handleScroll);
},

componentWillUnmount: function() {
    window.removeEventListener('scroll', this.handleScroll);
},

handleScroll: function(event) {
    let scrollTop = event.srcElement.body.scrollTop,
        itemTranslate = Math.min(0, scrollTop/3 - 60);

    this.setState({
      transform: itemTranslate
    });
},

回答by Con Antonakos

You can pass a function to the onScrollevent on the React element: https://facebook.github.io/react/docs/events.html#ui-events

您可以将函数传递给onScrollReact 元素上的事件:https: //facebook.github.io/react/docs/events.html#ui-events

<ScrollableComponent
 onScroll={this.handleScroll}
/>

Another answer that is similar: https://stackoverflow.com/a/36207913/1255973

另一个类似的答案:https: //stackoverflow.com/a/36207913/1255973

回答by adrian_reimer

to help out anyone here who noticed the laggy behavior / performance issues when using Austins answer, and wants an example using the refs mentioned in the comments, here is an example I was using for toggling a class for a scroll up / down icon:

为了帮助这里任何在使用 Austins 答案时注意到滞后行为/性能问题的人,并想要一个使用评论中提到的 refs 的示例,这是我用来切换类以进行向上/向下滚动图标的示例:

In the render method:

在渲染方法中:

<i ref={(ref) => this.scrollIcon = ref} className="fa fa-2x fa-chevron-down"></i>

In the handler method:

在处理程序方法中:

if (this.scrollIcon !== null) {
  if(($(document).scrollTop() + $(window).height() / 2) > ($('body').height() / 2)){
    $(this.scrollIcon).attr('class', 'fa fa-2x fa-chevron-up');
  }else{
    $(this.scrollIcon).attr('class', 'fa fa-2x fa-chevron-down');
  }
}

And add / remove your handlers the same way as Austin mentioned:

并以与 Austin 提到的相同的方式添加/删除您的处理程序:

componentDidMount(){
  window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount(){
  window.removeEventListener('scroll', this.handleScroll);
},

docs on the refs.

refs上的文档。

回答by robins_

My solution for making a responsive navbar ( position: 'relative' when not scrolling and fixed when scrolling and not at the top of the page)

我制作响应式导航栏的解决方案(位置:不滚动时为“相对”,滚动时固定而不是在页面顶部)

componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
}

componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
}
handleScroll(event) {
    if (window.scrollY === 0 && this.state.scrolling === true) {
        this.setState({scrolling: false});
    }
    else if (window.scrollY !== 0 && this.state.scrolling !== true) {
        this.setState({scrolling: true});
    }
}
    <Navbar
            style={{color: '#06DCD6', borderWidth: 0, position: this.state.scrolling ? 'fixed' : 'relative', top: 0, width: '100vw', zIndex: 1}}
        >

No performance issues for me.

对我来说没有性能问题。

回答by Jean-Marie Dalmasso

I found that I can't successfully add the event listener unless I pass true like so:

我发现除非我像这样传递 true 否则我无法成功添加事件侦听器:

componentDidMount = () => {
    window.addEventListener('scroll', this.handleScroll, true);
},

回答by giovannipds

An example using classNames, React hooksuseEffect, useStateand styled-jsx:

一个使用classNames,React钩子useEffectuseStatestyled-jsx 的例子

import classNames from 'classnames'
import { useEffect, useState } from 'react'

const Header = _ => {
  const [ scrolled, setScrolled ] = useState()
  const classes = classNames('header', {
    scrolled: scrolled,
  })
  useEffect(_ => {
    const handleScroll = _ => { 
      if (window.pageYOffset > 1) {
        setScrolled(true)
      } else {
        setScrolled(false)
      }
    }
    window.addEventListener('scroll', handleScroll)
    return _ => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [])
  return (
    <header className={classes}>
      <h1>Your website</h1>
      <style jsx>{`
        .header {
          transition: background-color .2s;
        }
        .header.scrolled {
          background-color: rgba(0, 0, 0, .1);
        }
      `}</style>
    </header>
  )
}
export default Header

回答by Richard

Function component example using useEffect:

使用 useEffect 的函数组件示例:

Note: You need to remove the event listener by returning a "clean up" function in useEffect. If you don't, every time the component updates you will have an additional window scroll listener.

注意:您需要通过在 useEffect 中返回“清理”函数来移除事件侦听器。如果不这样做,每次组件更新时,您都会有一个额外的窗口滚动侦听器。

import React, { useState, useEffect } from "react"

const ScrollingElement = () => {
  const [scrollY, setScrollY] = useState(0);

  function logit() {
    setScrollY(window.pageYOffset);
  }

  useEffect(() => {
    function watchScroll() {
      window.addEventListener("scroll", logit);
    }
    watchScroll();
    // Remove listener (like componentWillUnmount)
    return () => {
      window.removeEventListener("scroll", logit);
    };
  }, []);

  return (
    <div className="App">
      <div className="fixed-center">Scroll position: {scrollY}px</div>
    </div>
  );
}

回答by Sodium United

with hooks

带钩子

import React, { useEffect, useState } from 'react';

function MyApp () {

  const [offset, setOffset] = useState(0);

  useEffect(() => {
    window.onscroll = () => {
      setOffset(window.pageYOffset)
    }
  }, []);

  console.log(offset); 
};

回答by user2312410

If what you're interested in is a child component that's scrolling, then this example might be of help: https://codepen.io/JohnReynolds57/pen/NLNOyO?editors=0011

如果您感兴趣的是滚动的子组件,那么这个示例可能会有所帮助:https: //codepen.io/JohnReynolds57/pen/NLNOyO?editors=0011

class ScrollAwareDiv extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
    this.state = {scrollTop: 0}
  }

  onScroll = () => {
     const scrollTop = this.myRef.current.scrollTop
     console.log(`myRef.scrollTop: ${scrollTop}`)
     this.setState({
        scrollTop: scrollTop
     })
  }

  render() {
    const {
      scrollTop
    } = this.state
    return (
      <div
         ref={this.myRef}
         onScroll={this.onScroll}
         style={{
           border: '1px solid black',
           width: '600px',
           height: '100px',
           overflow: 'scroll',
         }} >
        <p>This demonstrates how to get the scrollTop position within a scrollable 
           react component.</p>
        <p>ScrollTop is {scrollTop}</p>
     </div>
    )
  }
}

回答by webpreneur

I solved the problem via using and modifying CSS variables. This way I do not have to modify the component state which causes performance issues.

我通过使用和修改 CSS 变量解决了这个问题。这样我就不必修改导致性能问题的组件状态。

index.css

索引文件

:root {
  --navbar-background-color: rgba(95,108,255,1);
}

Navbar.jsx

导航栏.jsx

import React, { Component } from 'react';
import styles from './Navbar.module.css';

class Navbar extends Component {

    documentStyle = document.documentElement.style;
    initalNavbarBackgroundColor = 'rgba(95, 108, 255, 1)';
    scrolledNavbarBackgroundColor = 'rgba(95, 108, 255, .7)';

    handleScroll = () => {
        if (window.scrollY === 0) {
            this.documentStyle.setProperty('--navbar-background-color', this.initalNavbarBackgroundColor);
        } else {
            this.documentStyle.setProperty('--navbar-background-color', this.scrolledNavbarBackgroundColor);
        }
    }

    componentDidMount() {
        window.addEventListener('scroll', this.handleScroll);
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleScroll);
    }

    render () {
        return (
            <nav className={styles.Navbar}>
                <a href="/">Home</a>
                <a href="#about">About</a>
            </nav>
        );
    }
};

export default Navbar;

Navbar.module.css

导航栏.module.css

.Navbar {
    background: var(--navbar-background-color);
}