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

Relaterte artikler