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

Ale*_*rez 111 javascript reactjs

我在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>
    );
  }
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为React不知道组件已更改,因此组件不会被重新呈现.

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

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

谢谢.

Aus*_*eco 206

你应该绑定监听器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
    });
},
Run Code Online (Sandbox Code Playgroud)

  • 我发现动画的滚动事件中的setState'ing是不稳定的.我不得不使用refs手动设置组件的样式. (22认同)
  • @yuji你可以避免需要通过在构造函数中绑定它来传递组件:`this.handleScroll = this.handleScroll.bind(this)`将把`handleScroll`中的这个绑定到组件而不是窗口. (8认同)
  • 对我不起作用,但是将 scrollTop 设置为 `event.target.scrollingElement.scrollTop` (4认同)
  • handleScroll 中的“this”会指向什么?在我的情况下,它是“窗口”而不是组件。我最终将组件作为参数传递 (2认同)

小智 37

带钩子

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

function MyApp () {

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

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

  console.log(offset); 
};
Run Code Online (Sandbox Code Playgroud)

  • 如果您选择这种方式,请务必在组件卸载时使用[cleanup 函数](https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup) 来删除侦听器。 (7认同)
  • 我不确定,但我不认为这会导致内存泄漏,因为`onscroll`是一个窗口上只有一个`onscroll`,而可以有很多`eventListener`。这就是为什么在这种情况下也不需要清理功能。不过,如果我错了,请纠正我。相关:/sf/ask/4252183281/ Between-window-onscroll-and-window-addeventlistener (4认同)
  • 我解决了真正的问题并更新了他的答案。 (2认同)

Con*_*kos 25

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

<ScrollableComponent
 onScroll={this.handleScroll}
/>
Run Code Online (Sandbox Code Playgroud)

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

  • 除了手动将事件监听器添加到提到的窗口元素@AustinGreco之外,此方法是否有任何好处/缺点? (4认同)
  • @Dennis 一个好处是您不必手动添加/删除事件侦听器。虽然这可能是一个简单的示例,如果您在整个应用程序中手动管理多个事件侦听器,很容易忘记在更新时正确删除它们,这可能会导致内存错误。如果可能,我将始终使用内置版本。 (2认同)
  • 值得注意的是,这会将滚动处理程序附加到组件本身,而不是附加到窗口,这是非常不同的事情。@Dennis onScroll的好处是它的跨浏览器和性能更高。如果可以使用它,可能应该使用,但是在诸如OP那样的情况下,它可能没有用。 (2认同)

adr*_*mer 17

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

在渲染方法中:

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

在处理程序方法中:

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');
  }
}
Run Code Online (Sandbox Code Playgroud)

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

componentDidMount(){
  window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount(){
  window.removeEventListener('scroll', this.handleScroll);
},
Run Code Online (Sandbox Code Playgroud)

关于裁判的文件.

  • 你救了我的一天!对于更新,您实际上不需要使用jquery来修改类名,因为它已经是本机DOM元素.所以你可以简单地做`this.scrollIcon.className =无论你想要什么. (4认同)
  • 这个解决方案破坏了React的封装,尽管我仍然不确定是否有没有滞后行为的方法-也许去抖动的滚动事件(可能在200-250毫秒)是这里的解决方案 (2认同)

gio*_*pds 17

一个使用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
Run Code Online (Sandbox Code Playgroud)


小智 15

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

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}}
        >
Run Code Online (Sandbox Code Playgroud)

对我来说没有性能问题.


Jea*_*sso 10

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

componentDidMount = () => {
    window.addEventListener('scroll', this.handleScroll, true);
},
Run Code Online (Sandbox Code Playgroud)

  • 来自 w3schools:[https://www.w3schools.com/jsref/met_document_addeventlistener.asp] `userCapture`:可选。一个布尔值,指定事件应该在捕获阶段还是在冒泡阶段执行。可能的值: true - 在捕获阶段执行事件处理程序 false - 默认。事件处理程序在冒泡阶段执行 (2认同)

Ric*_*ard 10

使用 useEffect 的函数组件示例:

注意:您需要通过在 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>
  );
}
Run Code Online (Sandbox Code Playgroud)


小智 7

如果您感兴趣的是滚动的子组件,那么这个示例可能会有所帮助: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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)


Cal*_*rón 6

我这里的赌注是使用具有新钩子的函数组件来解决它,但useEffect我认为正确的选项不是像以前的答案那样使用,而是有useLayoutEffect一个重要的原因:

该签名与 useEffect 相同,但它在所有 DOM 更改后同步触发。

这可以在React 文档中找到。如果我们使用useEffect相反的方法并重新加载已经滚动的页面,则滚动将为 false 并且我们的类将不会被应用,从而导致不需要的行为。

一个例子:

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

const Mycomponent = (props) => {
  const [scrolled, setScrolled] = useState(false)

  useLayoutEffect(() => {
    const handleScroll = e => {
      setScrolled(window.scrollY > 0)
    }

    window.addEventListener("scroll", handleScroll)

    return () => {
      window.removeEventListener("scroll", handleScroll)
    }
  }, [])

  ...

  return (
    <div className={scrolled ? "myComponent--scrolled" : ""}>
       ...
    </div>
  )
}
Run Code Online (Sandbox Code Playgroud)

该问题的可能解决方案可能是https://codepen.io/dcalderon/pen/mdJzOYq

const Item = (props) => { 
  const [scrollY, setScrollY] = React.useState(0)

  React.useLayoutEffect(() => {
    const handleScroll = e => {
      setScrollY(window.scrollY)
    }

    window.addEventListener("scroll", handleScroll)

    return () => {
      window.removeEventListener("scroll", handleScroll)
    }
  }, [])

  return (
    <div class="item" style={{'--scrollY': `${Math.min(0, scrollY/3 - 60)}px`}}>
      Item
    </div>
  )
}
Run Code Online (Sandbox Code Playgroud)


dow*_*owi 5

使用 React Hooks 更新答案

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

像这样使用:

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

useScrollDirection(direction => {
    console.log(direction)
  })
Run Code Online (Sandbox Code Playgroud)

这里是钩子:

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)
  })
}
Run Code Online (Sandbox Code Playgroud)