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

组件渲染方法是这样的:

  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。然而,这使得滚动无法使用,因为它非常慢。

有什么建议吗?


当前回答

用React Hooks更新答案

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

像这样使用:

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

其他回答

我在这里打赌是使用带有新钩子的函数组件来解决它,但不是像以前的答案那样使用useEffect,我认为正确的选择是useLayoutEffect,原因很重要:

签名与useEffect相同,但是它同步触发 在所有DOM突变之后。

这可以在React文档中找到。如果我们使用useEffect代替,并且重新加载已经滚动的页面,则scroll将为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>
  )
}

这个问题的一个可能的解决方案是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>
  )
}

使用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>
  );
}

如果你发现上面的答案对你不起作用,试试这个:

React.useEffect(() => {
    document.addEventListener('wheel', yourCallbackHere)
    return () => {
        document.removeEventListener('wheel', yourCallbackHere)
    }
}, [yourCallbackHere])

基本上,你需要尝试用文档代替窗口,用滚轮代替滚动。

编码快乐!

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

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

对我来说没有性能问题。

constructor() {
    super()
      this.state = {
        change: false
      }
  }

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
    console.log('add event');
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
    console.log('remove event');
  }

  handleScroll = e => {
    if (window.scrollY === 0) {
      this.setState({ change: false });
    } else if (window.scrollY > 0 ) {
      this.setState({ change: true });
    }
  }

render() {return (<div className="main" style={{boxShadow: this.state.change ?0px 6px 12px rgba(3,109,136,0.14):none}} ></div> .

我就是这么做的,而且效果很好。