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

IT技术 javascript reactjs
2021-02-01 01:08:45

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

组件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>
    );
  }

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

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

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

6个回答

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

像这样的东西:

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
    });
},
@yuji 您可以通过在构造函数中绑定 this 来避免需要传递组件:this.handleScroll = this.handleScroll.bind(this)将 this 绑定handleScroll到组件中,而不是窗口。
2021-03-21 01:08:45
对我不起作用,但是将 scrollTop 设置为 event.target.scrollingElement.scrollTop
2021-03-23 01:08:45
请注意, srcElement 在 Firefox 中不可用。
2021-03-26 01:08:45
handleScroll 中的“this”会指向什么?在我的情况下,它是“窗口”而不是组件。我最终将组件作为参数传递
2021-04-01 01:08:45
我发现动画滚动事件中的 setState'ing 不稳定。我不得不使用 refs 手动设置组件的样式。
2021-04-08 01:08:45

带钩子

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

function MyApp () {

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

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

  console.log(offset); 
};
我不确定,但我不认为这会导致内存泄漏,因为窗口上onscroll只有一个onscroll,而可以有很多eventListeners。这就是为什么在这种情况下也不需要清理功能的原因。但是,如果我错了,请纠正我。相关:stackoverflow.com/questions/60745475/...
2021-03-25 01:08:45
这就是我要找的!谢谢
2021-03-26 01:08:45
这是迄今为止最有效和最优雅的答案。谢谢你。
2021-03-29 01:08:45
如果你走这条路,一定要在组件卸载时使用清理功能来移除监听器。
2021-03-31 01:08:45
这甚至不使用addEventListener,并removeEventListener与有30个upvotes。我想知道我们有多少应用程序存在内存泄漏......正如布拉德利格里菲斯所说,使用useEffect清理。
2021-04-12 01:08:45

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

<ScrollableComponent
 onScroll={this.handleScroll}
/>

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

我根本无法让上面的例子起作用。有人可以请给我一个如何使用 ReactonScroll合成事件的链接吗?
2021-03-24 01:08:45
@youjin iOS 上的 IE 和 Safari 的某些版本在addEventListener滚动和滚动方面可能有点不稳定,而 jQuery 为您解决了很多问题(这就是 jQuery 的全部意义所在)。如果您好奇,请查看浏览器对两者的支持。我不确定 jQuery 是否比 vanilla js 性能更高(事实上我确定不是),但是将滚动处理程序附加到元素本身而不是windowis 因为事件不必通过要处理的 DOM。虽然总是有取舍。。
2021-03-28 01:08:45
值得注意的是,这将滚动处理程序附加到组件本身,而不是窗口,这是非常不同的事情。@Dennis onScroll 的好处是它更跨浏览器和更高的性能。如果您可以使用它,您可能应该使用它,但在像 OP 那样的情况下它可能没有用
2021-03-30 01:08:45
与手动向 @AustinGreco 提到的窗口元素添加事件侦听器相比,此方法有什么好处/缺点吗?
2021-04-03 01:08:45
@Dennis 一个好处是您不必手动添加/删除事件侦听器。虽然这可能是一个简单的示例,如果您在应用程序中手动管理多个事件侦听器,很容易忘记在更新时正确删除它们,这可能会导致内存错误。如果可能,我将始终使用内置版本。
2021-04-07 01:08:45

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

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}}
        >

对我来说没有性能问题。

没有性能问题,因为这不能解决问题中的视差挑战。
2021-03-21 01:08:45
您还可以使用假标题,它本质上只是一个占位符。所以你有你的固定标题,在它下面你有你的占位符 fakeheader 和 position:relative。
2021-03-27 01:08:45
这怎么可能工作?“这个”不在它的范围内..
2021-03-31 01:08:45

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

在渲染方法中:

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

在处理程序方法中:

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');
  }
}

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

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

refs上的文档

你救了我的一天!对于更新,此时您实际上不需要使用jquery来修改类名,因为它已经是原生DOM元素。所以你可以简单地做this.scrollIcon.className = whatever-you-want
2021-03-14 01:08:45
我也使用了这个解决方案,+1。我同意您不需要 jQuery:只需使用classNameclassList另外,我不需要window.addEventListener():我只使用了 React 的onScroll,只要你不更新 props/state,它就一样快!
2021-03-18 01:08:45
这个解决方案破坏了 React 封装,尽管我仍然不确定没有滞后行为的解决方法 - 也许去抖动的滚动事件(大约 200-250 毫秒)将是这里的解决方案
2021-03-25 01:08:45
nope debounced scroll 事件仅有助于使滚动更平滑(在非阻塞意义上),但更新状态需要 500 毫秒到一秒才能应用到 DOM 中:/
2021-03-29 01:08:45