web-dev-qa-db-fra.com

Mise à jour du style d'un composant onScroll dans React.js

J'ai construit un composant dans React qui est censé mettre à jour son propre style lors du défilement de fenêtre pour créer un effet de parallaxe.

La méthode du composant render ressemble à ceci:

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

Cela ne fonctionne pas car React ne sait pas que le composant a été modifié et par conséquent, le composant n'est pas rendu à nouveau.

J'ai essayé de stocker la valeur de itemTranslate dans l'état du composant et d'appeler setState dans le rappel de défilement. Cependant, cela rend le défilement inutilisable car il est terriblement lent.

Toute suggestion sur la façon de faire cela?

Merci.

76
Alejandro Pérez

Vous devez lier l'auditeur dans componentDidMount, de cette manière, il ne sera créé qu'une seule fois. Vous devriez pouvoir stocker le style dans l'état, l'auditeur était probablement à l'origine des problèmes de performances.

Quelque chose comme ça:

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
    });
},
163
Austin Greco

Vous pouvez transmettre une fonction à l'événement onScroll sur l'élément React: https://facebook.github.io/react/docs/events.html#ui-events

<ScrollableComponent
 onScroll={this.handleScroll}
/>

Une autre réponse similaire est: https://stackoverflow.com/a/36207913/1255973

19
Con Antonakos

pour aider ceux qui ont remarqué les problèmes de comportement/performance lors de l’utilisation de la réponse d’Austins, et qui souhaitent utiliser un exemple utilisant les références mentionnées dans les commentaires, voici un exemple que j’utilisais pour basculer d’une classe à l’autre:

Dans la méthode de rendu:

<i ref={(ref) => this.scrollIcon = ref} className="fa fa-2x fa-chevron-down"></i>

Dans la méthode du gestionnaire:

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

Et ajoutez/supprimez vos gestionnaires de la même manière qu'Austin a mentionné:

componentDidMount(){
  window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount(){
  window.removeEventListener('scroll', this.handleScroll);
},

docs sur les refs .

16
adrian_reimer

Ma solution pour créer une barre de navigation réactive (position: 'relative' lorsque le défilement n'est pas défini et fixée lors du défilement et non en haut de la page)

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

Aucun problème de performance pour moi.

14
robins_

J'ai constaté que je ne pouvais pas ajouter l'écouteur d'événements avec succès, sauf si je transmettais true comme suit:

componentDidMount = () => {
    window.addEventListener('scroll', this.handleScroll, true);
},
4
Jean-Marie Dalmasso

Si ce qui vous intéresse est un composant enfant qui défile, cet exemple peut vous être utile: 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>
    )
  }
}
1
user2312410

Exemple de composant de fonction utilisant useEffect:

Note: Vous devez supprimer l'écouteur d'événement en retournant une fonction "nettoyer" dans useEffect. Sinon, chaque fois que le composant sera mis à jour, vous aurez un écouteur de défilement de fenêtre supplémentaire.

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

Pour développer la réponse de @ Austin, vous devez ajouter this.handleScroll = this.handleScroll.bind(this) à votre constructeur:

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

Cela donne à handleScroll() l'accès à l'étendue appropriée lorsqu'il est appelé à partir du programme d'écoute d'événements. 

Sachez également que vous ne pouvez pas utiliser la .bind(this) dans les méthodes addEventListener ou removeEventListener car elles renverront chacune des références à différentes fonctions et l'événement ne sera pas supprimé lorsque le composant sera démonté.

0
nbwoodward

J'ai résolu le problème en utilisant et en modifiant des variables CSS. De cette façon, je n'ai pas à modifier l'état du composant, ce qui entraîne des problèmes de performances.

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);
}
0
Zsolt Gulyás