我在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。然而,这使得滚动无法使用,因为它非常慢。
有什么建议吗?
我在这里打赌是使用带有新钩子的函数组件来解决它,但不是像以前的答案那样使用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>
)
}
我通过使用和修改CSS变量解决了这个问题。这样我就不必修改会导致性能问题的组件状态。
index.css
:root {
--navbar-background-color: rgba(95,108,255,1);
}
Navbar.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
.Navbar {
background: var(--navbar-background-color);
}
要扩展@Austin的回答,你应该加上这个。handleScroll = this.handleScroll.bind(this)到你的构造函数:
constructor(props){
this.handleScroll = this.handleScroll.bind(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
});
},
...
这使handleScroll()在从事件侦听器调用时可以访问适当的范围。
还要注意,你不能在addEventListener或removeEventListener方法中执行.bind(this),因为它们将各自返回对不同函数的引用,并且当组件卸载时,事件不会被删除。
下面是另一个使用HOOKS fontAwesomeIcon和Kendo UI React的例子
[![这里截图][1]][1]
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const ScrollBackToTop = () => {
const [show, handleShow] = useState(false);
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY > 1200) {
handleShow(true);
} else handleShow(false);
});
return () => {
window.removeEventListener('scroll');
};
}, []);
const backToTop = () => {
window.scroll({ top: 0, behavior: 'smooth' });
};
return (
<div>
{show && (
<div className="backToTop text-center">
<button className="backToTop-btn k-button " onClick={() => backToTop()} >
<div className="d-none d-xl-block mr-1">Top</div>
<FontAwesomeIcon icon="chevron-up"/>
</button>
</div>
)}
</div>
);
};
export default ScrollBackToTop;```
[1]: https://i.stack.imgur.com/ZquHI.png
如果您感兴趣的是滚动的子组件,那么这个示例可能会有所帮助: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>
)
}
}