ReactJS - Obtener la altura de un elemento

121

¿Cómo puedo obtener la altura de un elemento después de que React renderice ese elemento?

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

RESULTADO

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

Está calculando la altura del contenedor antes del render (36px). Quiero obtener la altura después del render. El resultado correcto debería ser 18px en este caso. jsfiddle

faia20
fuente
1
Esta no es una pregunta de reacción, sino más bien una pregunta de Javascript y DOM. Debe intentar averiguar qué evento DOM debe usar para encontrar la altura final de su elemento. En el controlador de eventos, puede usar setStatepara asignar el valor de altura a una variable de estado.
mostruash

Respuestas:

28

Ver este violín (en realidad actualizó el suyo)

Debe conectarse al componentDidMountque se ejecuta después del método de renderizado. Allí, obtienes la altura real del elemento.

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
fuente
15
incorrecto. vea la respuesta de Paul Vincent Beigang a continuación
ekatz
1
En mi humilde opinión, un componente no debe saber el nombre de su contenedor
Andrés
156

A continuación se muestra un ejemplo de ES6 actualizado utilizando una ref .

Recuerde que tenemos que usar un componente de la clase React ya que necesitamos acceder al método Lifecycle componentDidMount()porque solo podemos determinar la altura de un elemento después de que se representa en el 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'))

Puede encontrar el ejemplo de ejecución aquí: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
fuente
6
Esto funciona, pero recibo varios errores de eslint para la guía de estilo de Airbnb: <div ref={ divElement => this.divElement = divElement}>{children}</div>lanza "[eslint] La función de flecha no debería devolver la asignación. (No-return-assign)" y this.setState({ height });lanza "[eslint] No use setState en componentDidMount (react / no- did-mount-set-state) ". ¿Alguien tiene una solución compatible con la guía de estilo?
Timo
7
Envuelva la tarea entre llaves para que se comporte como una función (en lugar de una expresión), asíref={ divElement => {this.divElement = divElement}}
teletipo
3
Esta debería ser la respuesta aceptada :) Además, si desea obtener el tamaño del elemento después de que se actualice, también puede usar el método componentDidUpdate.
jjimenez
4
¿Existe una alternativa para llamar a this.setState () en componentDidMount con este ejemplo?
danjones_mcr
3
Buena solución. Pero no funcionará para diseños receptivos. Si la altura del encabezado era de 50 px para el escritorio, y el usuario reduce el tamaño de la ventana y ahora la altura se convierte en 100 px, esta solución no tomará la altura actualizada. Necesitan actualizar la página para obtener los efectos modificados.
TheCoder
97

Para aquellos que estén interesados ​​en usar react hooks, esto podría ayudarlos a comenzar.

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>
  )
}
Señor 14
fuente
11
Gracias por tu respuesta, me ayudó a empezar. Sin embargo, 2 preguntas: (1) Para estas tareas de medición de diseño, ¿deberíamos usar en useLayoutEffectlugar de useEffect? Los documentos parecen indicar que deberíamos. Vea la sección "sugerencia" aquí: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Para estos casos en los que solo necesitamos una referencia a un elemento hijo, ¿deberíamos usar en useReflugar de createRef? Los documentos parecen indicar que useRefes más apropiado para este caso de uso. Ver: reactjs.org/docs/hooks-reference.html#useref
Jason Frank
Acabo de implementar una solución que utiliza los dos elementos que sugiero. Parecen funcionar bien, pero es posible que conozca algunas desventajas de estas opciones. ¡Gracias por tu ayuda!
Jason Frank
4
@JasonFrank Buenas preguntas. 1) Tanto useEffect como useLayoutEffect se activarán después del diseño y la pintura. Sin embargo, useLayoutEffect se activará sincrónicamente antes de la siguiente pintura. Yo diría que si realmente necesita consistencia visual, use useLayoutEffect, de lo contrario, useEffect debería ser suficiente. 2) ¡Tienes razón en eso! En un componente funcional, createRef creará una nueva referencia cada vez que se procese el componente. Usar useRef es la mejor opción. Actualizaré mi respuesta. ¡Gracias!
Sr. 14
Esto me ayudó a entender cómo usar una referencia con ganchos. Terminé yendo con useLayoutEffectdespués de leer los otros comentarios aquí. Parece funcionar bien. :)
GuiDoody
1
Estaba configurando las dimensiones de mi componente useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), y mi IDE (WebStorm) me sugirió esto: ESLint: React Hook useEffect contiene una llamada a 'setDimensions'. Sin una lista de dependencias, esto puede conducir a una cadena infinita de actualizaciones. Para solucionar este problema, pase [] como segundo argumento al gancho useEffect. (react-hooks / exhaustive-deps) , así que pase []como segundo argumento a suuseEffect
Akshdeep Singh
9

También querrá usar referencias en el elemento en lugar de usar document.getElementById, es algo un poco más robusto.

yoyodunno
fuente
@doublejosh básicamente tiene razón, pero ¿qué hacer si necesita mezclar, por ejemplo, angularJsy reactmientras migra de uno a otro? Hay casos en los que realmente se necesita una manipulación DOM directa
messerbill
2

La respuesta de mi 2020 (o 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>
    )
}

Use su imaginación para lo que falta aquí (WidgetHead), reactstrapes algo que puede encontrar en npm: reemplace innerRefcon refpara un elemento dom heredado (por ejemplo, a <div>).

useEffect o useLayoutEffect

Se dice que el último es sincrónico para los cambios.

useLayoutEffect(o useEffect) segundo argumento

El segundo argumento es una matriz y se verifica antes de ejecutar la función en el primer argumento.

solía

[yo mismo actual, yo mismo actual? yo mismo actual.clienteHeight: 0]

porque yo mismo.currente es nulo antes de renderizar, y es bueno no verificarlo, el segundo parámetro al final myself.current.clientHeightes lo que quiero verificar para ver si hay cambios.

lo que estoy resolviendo aquí (o tratando de resolver)

Estoy resolviendo aquí el problema del widget en una cuadrícula que cambia su altura por su propia voluntad, y el sistema de cuadrícula debe ser lo suficientemente elástico para reaccionar ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
fuente
1
gran respuesta, pero se habría beneficiado de un ejemplo más simple. Básicamente, la respuesta de whiteknight pero con useLayoutEffectun div real para vincular la referencia.
bot19
1

Utilizando con ganchos:

Esta respuesta sería útil si su dimensión de contenido cambia después de la carga.

onreadystatechange : ocurre cuando cambia el estado de carga de los datos que pertenecen a un elemento o documento HTML. El evento onreadystatechange se activa en un documento HTML cuando el estado de carga del contenido de la página ha cambiado.

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

Estaba tratando de trabajar con una inserción de reproductor de video de YouTube cuyas dimensiones pueden cambiar después de la carga.

iamakshatjain
fuente
0

Aquí hay otro si necesita un evento de cambio de tamaño de ventana:

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

bolígrafo de código

Gfast2
fuente
0

Una solución alternativa, en caso de que desee recuperar el tamaño de un elemento React de forma síncrona sin tener que representar visiblemente el elemento, puede usar ReactDOMServer y DOMParser .

Utilizo esta función para obtener la altura de un renderizador de elementos de mi lista cuando utilizo react-window ( react-virtualized ) en lugar de tener que codificar el itemSizeaccesorio requerido para 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
fuente