ReactJS - Obtenir la hauteur d'un élément

121

Comment puis-je obtenir la hauteur d'un élément après que React a rendu cet élément?

HTML

<div id="container">
<!-- This element's contents will be replaced with your component. -->
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
</div>

ReactJS

var DivSize = React.createClass({

  render: function() {
    let elHeight = document.getElementById('container').clientHeight
    return <div className="test">Size: <b>{elHeight}px</b> but it should be 18px after the render</div>;
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);

RÉSULTAT

Size: 36px but it should be 18px after the render

Il calcule la hauteur du conteneur avant le rendu (36px). Je veux obtenir la hauteur après le rendu. Le bon résultat devrait être 18px dans ce cas. jsfiddle

faia20
la source
1
Ce n'est pas une question de réaction mais plutôt une question Javascript et DOM. Vous devriez essayer de déterminer quel événement DOM vous devez utiliser pour trouver la hauteur finale de votre élément. Dans le gestionnaire d'événements, vous pouvez utiliser setStatepour affecter la valeur de hauteur à une variable d'état.
mostruash

Réponses:

28

Voir ce violon (en fait mis à jour le vôtre)

Vous devez vous connecter à componentDidMountlaquelle est exécutée après la méthode de rendu. Là, vous obtenez la hauteur réelle de l'élément.

var DivSize = React.createClass({
    getInitialState() {
    return { state: 0 };
  },

  componentDidMount() {
    const height = document.getElementById('container').clientHeight;
    this.setState({ height });
  },

  render: function() {
    return (
        <div className="test">
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    );
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>

<div id="container">
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
    <!-- This element's contents will be replaced with your component. -->
</div>
Andreyco
la source
16
Pas correcte. voir la réponse de Paul Vincent Beigang ci
ekatz
1
À mon humble avis, un composant ne doit pas connaître le nom de son conteneur
Andrés
157

Voici un exemple ES6 à jour utilisant une réf .

N'oubliez pas que nous devons utiliser un composant de classe React car nous devons accéder à la méthode Lifecycle componentDidMount()car nous ne pouvons déterminer la hauteur d'un élément qu'après son rendu dans le DOM.

import React, {Component} from 'react'
import {render} from 'react-dom'

class DivSize extends Component {

  constructor(props) {
    super(props)

    this.state = {
      height: 0
    }
  }

  componentDidMount() {
    const height = this.divElement.clientHeight;
    this.setState({ height });
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    )
  }
}

render(<DivSize />, document.querySelector('#container'))

Vous pouvez trouver l'exemple en cours ici: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
la source
6
Cela fonctionne mais j'obtiens plusieurs erreurs eslint pour Airbnb styleguide: <div ref={ divElement => this.divElement = divElement}>{children}</div>jette "[eslint] La fonction Arrow ne doit pas retourner d'affectation. (No-return-assign)" et this.setState({ height });lance "[eslint] Ne pas utiliser setState dans componentDidMount (react / no- did-mount-set-state) ". Quelqu'un a-t-il une solution compatible avec Styleguide?
Timo
7
Enveloppez le devoir entre accolades afin qu'il se comporte comme une fonction (plutôt qu'une expression), comme ceciref={ divElement => {this.divElement = divElement}}
teletypist
3
Cela pourrait être la réponse acceptée :) De plus, si vous voulez obtenir la taille de l'élément après la mise à jour de l'élément, vous pouvez également utiliser la méthode componentDidUpdate.
jjimenez
4
existe-t-il une alternative à l'appel de this.setState () dans componentDidMount avec cet exemple?
danjones_mcr
3
Bonne solution. Mais ne fonctionnera pas pour les conceptions réactives. Si la hauteur de l'en-tête était de 50px pour le bureau et que l'utilisateur réduit la taille de la fenêtre et que la hauteur devient maintenant 100px, cette solution ne prendra pas la hauteur mise à jour. Ils doivent actualiser la page pour obtenir les effets modifiés.
TheCoder
100

Pour ceux qui sont intéressés à utiliser react hooks, cela pourrait vous aider à démarrer.

import React, { useState, useEffect, useRef } from 'react'

export default () => {
  const [height, setHeight] = useState(0)
  const ref = useRef(null)

  useEffect(() => {
    setHeight(ref.current.clientHeight)
  })

  return (
    <div ref={ref}>
      {height}
    </div>
  )
}
Monsieur 14
la source
11
Merci pour votre réponse - cela m'a aidé à démarrer. 2 questions cependant: (1) Pour ces tâches de mesure de disposition, devrions-nous utiliser à la useLayoutEffectplace de useEffect? Les documents semblent indiquer que nous devrions. Voir la section "astuce" ici: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Pour ces cas où nous avons juste besoin d'une référence à un élément enfant, devrions-nous utiliser à la useRefplace de createRef? La documentation semble indiquer que le useRefest plus approprié pour ce cas d'utilisation. Voir: reactjs.org/docs/hooks-reference.html#useref
Jason Frank
Je viens de mettre en œuvre une solution qui utilise les deux éléments que je suggère. Ils semblent bien fonctionner, mais vous connaissez peut-être certains inconvénients de ces choix? Merci de votre aide!
Jason Frank
4
@JasonFrank Bonnes questions. 1) useEffect et useLayoutEffect seront tous deux déclenchés après la mise en page et la peinture. Cependant, useLayoutEffect se déclenchera de manière synchrone avant la prochaine peinture. Je dirais que si vous avez vraiment besoin de cohérence visuelle, utilisez useLayoutEffect, sinon useEffect devrait suffire. 2) Vous avez raison! Dans un composant fonctionnel, createRef créera une nouvelle référence à chaque fois que le composant est rendu. Utiliser useRef est la meilleure option. Je mettrai à jour ma réponse. Merci!
M. 14
Cela m'a aidé à comprendre comment utiliser une référence avec des crochets. J'ai fini par y aller useLayoutEffectaprès avoir lu les autres commentaires ici. Semble bien fonctionner. :)
GuiDoody
1
J'étais en train de définir les dimensions de mon composant useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), et mon IDE (WebStorm) m'a suggéré ceci: ESLint: React Hook useEffect contient un appel à 'setDimensions'. Sans une liste de dépendances, cela peut conduire à une chaîne infinie de mises à jour. Pour résoudre ce problème, passez [] comme deuxième argument au hook useEffect. (react-hooks / exhaust-deps) , alors passez []comme deuxième argument à votreuseEffect
Akshdeep Singh
9

Vous voudrez également utiliser des références sur l'élément au lieu d'utiliser document.getElementById, c'est juste une chose légèrement plus robuste.

yoyodunno
la source
@doublejosh en gros vous avez raison mais que faire si vous avez besoin de mélanger par exemple angularJset reacten migrant de l'un à l'autre? Il y a des cas où une manipulation directe du DOM est vraiment nécessaire
messerbill
2

Ma réponse de 2020 (ou 2019)

import React, {Component, useRef, useLayoutEffect} from 'react';
import { useDispatch } from 'react-redux';
import { Toast, ToastBody, ToastHeader } from 'reactstrap';

import {WidgetHead} from './WidgetHead';

export const Widget = ({title, toggle, reload, children, width, name}) => {
    let myself = useRef(null);
    const dispatch = useDispatch();
    useLayoutEffect(()=>{
        if (myself.current) {
            const height = myself.current.clientHeight
            dispatch({type:'GRID_WIDGET_HEIGHT', widget:name, height})
        }
    }, [myself.current, myself.current?myself.current.clientHeight:0])

    return (
        <Toast innerRef={myself}>
            <WidgetHead title={title}
                toggle={toggle}
                reload={reload} />
            <ToastBody>
            {children}
            </ToastBody>
        </Toast>
    )
}

laissez utiliser votre imagination pour ce qui manque ici (WidgetHead), reactstrapest quelque chose que vous pouvez trouver sur npm: remplacez innerRefpar refun élément dom hérité (disons a <div>).

useEffect ou useLayoutEffect

Le dernier est dit synchrone pour les changements

useLayoutEffect(ou useEffect) deuxième argument

Le deuxième argument est un tableau et il est vérifié avant d'exécuter la fonction dans le premier argument.

j'ai utilisé

[moi-même.current, moi-même.current? moi-même.current.clientHeight: 0]

car moi-même.current est nul avant le rendu, et c'est une bonne chose de ne pas vérifier, le deuxième paramètre à la fin myself.current.clientHeightest ce que je veux vérifier pour les changements.

ce que je résous ici (ou essaie de résoudre)

Je résous ici le problème du widget sur une grille qui change sa hauteur de sa propre volonté, et le système de grille doit être suffisamment élastique pour réagir ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
la source
1
excellente réponse, mais aurait bénéficié d'un exemple plus simple. Fondamentalement, la réponse de Whiteknight mais avec useLayoutEffectun div réel auquel lier l'arbitre.
bot19
1

Utilisation avec des crochets:

Cette réponse serait utile si votre dimension de contenu change après le chargement.

onreadystatechange : se produit lorsque l'état de chargement des données appartenant à un élément ou à un document HTML change. L'événement onreadystatechange est déclenché sur un document HTML lorsque l'état de chargement du contenu de la page a changé.

import {useState, useEffect, useRef} from 'react';
const ref = useRef();
useEffect(() => {
    document.onreadystatechange = () => {
      console.log(ref.current.clientHeight);
    };
  }, []);

J'essayais de travailler avec un lecteur vidéo YouTube intégré dont les dimensions peuvent changer après le chargement.

iamakshatjain
la source
1

Au lieu d'utiliser document.getElementById(...), une meilleure solution (à jour) consiste à utiliser le hook React useRef qui stocke une référence au composant / élément, combiné avec un hook useEffect , qui se déclenche aux rendus de composant.

import React, {useState, useEffect, useRef} from 'react';

export default App = () => {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);

  useEffect(() => {
    setHeight(ref.current.clientHeight);
  }, []); //empty dependency array so it only runs once at render

  return (
    <div ref={elementRef}>
      {height}
    </div>
  )
}
charri
la source
0

J'ai trouvé le package npm utile https://www.npmjs.com/package/element-resize-detector

Un écouteur optimisé pour redimensionner les éléments dans plusieurs navigateurs.

Peut l'utiliser avec un composant React ou un composant fonctionnel (particulièrement utile pour les hooks de réaction)

qmn1711
la source
0

En voici un autre si vous avez besoin d'un événement de redimensionnement de la fenêtre:

class DivSize extends React.Component {

  constructor(props) {
    super(props)

    this.state = {
      width: 0,
      height: 0
    }
    this.resizeHandler = this.resizeHandler.bind(this);
  }

  resizeHandler() {
    const width = this.divElement.clientWidth;
    const height = this.divElement.clientHeight;
    this.setState({ width, height });
  }

  componentDidMount() {
    this.resizeHandler();
    window.addEventListener('resize', this.resizeHandler);
  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.resizeHandler);
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: widht: <b>{this.state.width}px</b>, height: <b>{this.state.height}px</b>
      </div>
    )
  }
}

ReactDOM.render(<DivSize />, document.querySelector('#container'))

code stylo

Gfast2
la source
0

Une solution alternative, au cas où vous voudriez récupérer la taille d'un élément React de manière synchrone sans avoir à rendre visiblement l'élément, vous pouvez utiliser ReactDOMServer et DOMParser .

J'utilise cette fonction pour obtenir la hauteur d'un rendu d'élément de ma liste lors de l'utilisation de react-window ( react-virtualized ) au lieu d'avoir à coder en dur l' itemSizeaccessoire requis pour un FixedSizeList .

utilities.js:

/**
 * @description Common and reusable functions 
 * 
 * @requires react-dom/server
 * 
 * @public
 * @module
 * 
 */
import ReactDOMServer from "react-dom/server";

/**
 * @description Retrieve the width and/or heigh of a React element without rendering and committing the element to the DOM.
 * 
 * @param {object} elementJSX - The target React element written in JSX.
 * @return {object} 
 * @public
 * @function
 * 
 * @example
 * 
 * const { width, height } = getReactElementSize( <div style={{ width: "20px", height: "40px" }} ...props /> );
 * console.log(`W: ${width}, H: ${height});  // W: 20, H: 40
 * 
 */
const getReactElementSize = (elementJSX) => {

    const elementString = ReactDOMServer.renderToStaticMarkup(elementJSX);
    const elementDocument = new DOMParser().parseFromString(elementString, "text/html");
    const elementNode = elementDocument.getRootNode().body.firstChild;

    const container = document.createElement("div");
    const containerStyle = {

        display: "block",
        position: "absolute",
        boxSizing: "border-box",
        margin: "0",
        padding: "0",
        visibility: "hidden"
    };

    Object.assign(container.style, containerStyle);

    container.appendChild(elementNode);
    document.body.appendChild(container);

    const width = container.clientWidth;
    const height = container.clientHeight;

    container.removeChild(elementNode);
    document.body.removeChild(container);

    return {

        width,
        height
    };
};

/**
 * Export module
 * 
 */
export {

    getReactElementSize
};
TheDarkIn1978
la source