我在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。然而,这使得滚动无法使用,因为它非常慢。
有什么建议吗?
如果您感兴趣的是滚动的子组件,那么这个示例可能会有所帮助: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>
)
}
}
要扩展@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),因为它们将各自返回对不同函数的引用,并且当组件卸载时,事件不会被删除。
如果您感兴趣的是滚动的子组件,那么这个示例可能会有所帮助: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>
)
}
}
我的解决方案,使一个响应式导航栏(位置:“相对”时不滚动和固定时滚动,而不是在页面顶部)
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> .
我就是这么做的,而且效果很好。
用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)
})
}