javascript - Oppdater stilen til en komponent ved onScroll i React.js
Jeg har bygget en komponent i React som skal oppdatere sin egen stil ved vindusscroll for å lage en parallax-effekt.
Komponentens render-metode ser slik ut:
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>
);
}
Dette fungerer ikke fordi React ikke vet at komponenten har endret seg, og derfor blir ikke komponenten rendret på nytt.
Jeg har prøvd å lagre verdien av itemTranslate i komponentens tilstand, og å kalle setState i scroll-callbacken. Dette gjør imidlertid scrolling ubrukelig da dette er fryktelig tregt.
Noen forslag til hvordan man kan gjøre dette?
Svar #1
Du bør knytte lytteren i componentDidMount, på den måten blir den bare opprettet én gang. Du bør kunne lagre stilen i state, lytteren var sannsynligvis årsaken til ytelsesproblemer.
Noe sånt som dette:
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
});
},
Svar #2
med kroker:
import React, { useEffect, useState } from 'react';
function MyApp () {
const [offset, setOffset] = useState(0);
useEffect(() => {
const onScroll = () => setOffset(window.scrollY);
// clean up code
window.removeEventListener('scroll', onScroll);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
console.log(offset);
};
Svar #3
Du kan sende en funksjon til onScroll-hendelsen på React-elementet: https://facebook.github.io/react/docs/events.html#ui-events
<ScrollableComponent
onScroll={this.handleScroll}
/>
Et annet svar som er likt: https://stackoverflow.com/a/36207913/1255973
2023-10 Oppdatering
- Oppdaterte React.dev-dokumenter: https://react.dev/reference/react-dom/components/common#common-props
- MDN-dokumentasjon: https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event
Svar #4
Min løsning for å lage en responsiv navigasjonslinje (posisjon: 'relative' når man ikke blar og fast når man blar og ikke er på toppen av siden)
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}}
>
Ingen ytelsesproblemer for meg.
Svar #5
for å hjelpe alle her som har lagt merke til treg oppførsel/ytelsesproblemer når de bruker Austins svar, og ønsker et eksempel som bruker referansene nevnt i kommentarene, her er et eksempel jeg brukte for å bytte klasse for et ikonsymbol for rulling opp/ned:
I render-metoden:
<i ref={(ref) => this.scrollIcon = ref} className="fa fa-2x fa-chevron-down"></i>
I håndteringsmetoden:
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');
}
}
Og legg til/fjern håndtererne dine på samme måte som Austin nevnte:
componentDidMount(){
window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount(){
window.removeEventListener('scroll', this.handleScroll);
},
dokumenter om referansene.
Svar #6
Et eksempel som bruker classNames, React hooks useEffect, useState og styled-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
Svar #7
Jeg oppdaget at jeg ikke kan legge til hendelseslytter med suksess med mindre jeg sender true slik:
componentDidMount = () => {
window.addEventListener('scroll', this.handleScroll, true);
},
Svar #8
Eksempel på funksjonskomponent som bruker useEffect:
Merk: Du må fjerne hendelseslytter ved å returnere en «oppryddings»-funksjon i useEffect. Gjør du ikke det, vil du hver gang komponenten oppdateres få en ekstra vindu rullelytter.
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>
);
}
