我在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。然而,这使得滚动无法使用,因为它非常慢。
有什么建议吗?
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> .
我就是这么做的,而且效果很好。
我的解决方案,使一个响应式导航栏(位置:“相对”时不滚动和固定时滚动,而不是在页面顶部)
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}}
>
对我来说没有性能问题。
我在这里打赌是使用带有新钩子的函数组件来解决它,但不是像以前的答案那样使用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>
)
}
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> .
我就是这么做的,而且效果很好。