ReactJS - obter a altura de um elemento

121

Como posso obter a altura de um elemento depois que o React renderiza esse 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

Ele está calculando a altura do contêiner antes da renderização (36px). Quero obter a altura após a renderização. O resultado certo deve ser 18 px neste caso. jsfiddle

faia20
fonte
1
Esta não é uma pergunta de reação, mas sim uma pergunta de Javascript e DOM. Você deve tentar descobrir qual evento DOM deve usar para encontrar a altura final do seu elemento. No manipulador de eventos, você pode usar setStatepara atribuir o valor da altura a uma variável de estado.
mostruash

Respostas:

29

Veja este violino (na verdade atualizou o seu)

Você precisa ligar para componentDidMountqual é executado após o método de renderização. Lá, você obtém a altura real do 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
fonte
15
incorreto. veja a resposta de Paul Vincent Beigang abaixo
ekatz de
1
IMHO, um componente não deve saber o nome de seu contêiner
Andrés
156

A seguir está um exemplo ES6 atualizado usando uma ref .

Lembre-se de que temos que usar um componente da classe React, pois precisamos acessar o método Lifecycle componentDidMount()porque só podemos determinar a altura de um elemento depois que ele é renderizado no 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'))

Você pode encontrar o exemplo de execução aqui: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
fonte
6
Isso funciona, mas estou recebendo vários erros eslint para o guia de estilo do Airbnb: <div ref={ divElement => this.divElement = divElement}>{children}</div>lança "[eslint] A função de seta não deve retornar atribuição. (No-return-assign)" e this.setState({ height });lança "[eslint] Não use setState em componentDidMount (react / no- did-mount-set-state) ". Alguém tem uma solução compatível com o guia de estilo?
Timo
7
Enrole a atribuição entre colchetes para que se comporte como uma função (ao invés de uma expressão), assimref={ divElement => {this.divElement = divElement}}
teletipista
3
Esta deve ser a resposta aceita :) Além disso, se você deseja obter o tamanho do elemento após a atualização do elemento, você pode usar o método componentDidUpdatetambém.
jjimenez
4
existe uma alternativa para chamar this.setState () em componentDidMount com este exemplo?
danjones_mcr
3
Boa solução. Mas não funcionará para designs responsivos. Se a altura do cabeçalho era 50px para desktop e o usuário reduzia o tamanho da janela, e agora a altura se tornava 100px, esta solução não assumirá a altura atualizada. Eles precisam atualizar a página para obter os efeitos alterados.
TheCoder
96

Para aqueles que estão interessados ​​em usar react hooks, isso pode ajudá-lo a começar.

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>
  )
}
Sr. 14
fonte
11
Obrigado pela sua resposta - ajudou-me a começar. Porém, 2 perguntas: (1) Para essas tarefas de medição de layout, devemos usar em useLayoutEffectvez de useEffect? Os documentos parecem indicar que devemos. Veja a seção "dica" aqui: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Para esses casos em que precisamos apenas de uma referência a um elemento filho, devemos usar em useRefvez de createRef? Os documentos parecem indicar que useRefé mais apropriado para esse caso de uso. Veja: reactjs.org/docs/hooks-reference.html#useref
Jason Frank
Acabei de implementar uma solução que usa os dois itens que estou sugerindo. Eles parecem funcionar bem, mas você pode saber quais são as desvantagens dessas escolhas? Obrigado pela ajuda!
Jason Frank
4
@JasonFrank. Boas perguntas. 1) UseEffect e useLayoutEffect serão disparados após o layout e a pintura. No entanto, useLayoutEffect será acionado de forma síncrona antes da próxima pintura. Eu diria que se você realmente precisa de consistência visual, use useLayoutEffect, caso contrário, useEffect deve ser suficiente. 2) Você está certo sobre isso! Em um componente funcional, o createRef criará um novo ref toda vez que o componente estiver sendo renderizado. Usar useRef é a melhor opção. Vou atualizar minha resposta. Obrigado!
Sr. 14 de
Isso me ajudou a entender como usar um ref com ganchos. Acabei indo com useLayoutEffectdepois de ler os outros comentários aqui. Parece funcionar bem. :)
GuiDoody 01 de
1
Eu estava definindo as dimensões do meu componente useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))e meu IDE (WebStorm) me sugeriu o seguinte: ESLint: React Hook useEffect contém uma chamada para 'setDimensions'. Sem uma lista de dependências, isso pode levar a uma cadeia infinita de atualizações. Para corrigir isso, passe [] como um segundo argumento para o gancho useEffect. (react-hooks / exaustive-deps) , então passe []como segundo argumento para seuuseEffect
Akshdeep Singh
9

Você também gostaria de usar refs no elemento em vez de usar document.getElementById, é apenas uma coisa um pouco mais robusta.

Yoyodunno
fonte
@doublejosh basicamente, você está certo, mas o que fazer se você precisa de confusão, por exemplo, angularJse reactao migrar de um para o outro? Há casos em que uma manipulação direta de DOM é realmente necessária
messerbill
2

Minha resposta 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>
    )
}

deixe usar sua imaginação para ver o que está faltando aqui (WidgetHead), reactstrapé algo que você pode encontrar no npm: substitua innerRefpor refpor um elemento dom legado (digamos a <div>).

useEffect ou useLayoutEffect

O último é considerado síncrono para mudanças

useLayoutEffect(ou useEffect) segundo argumento

O segundo argumento é uma matriz e é verificado antes de executar a função no primeiro argumento.

eu usei

[eu.corrente, eu.corrente? eu.corrente.clientHeight: 0]

porque eu.atual é nulo antes da renderização, e isso é uma boa coisa para não verificar, o segundo parâmetro no final myself.current.clientHeighté o que eu quero verificar se há alterações.

o que estou resolvendo aqui (ou tentando resolver)

Estou resolvendo aqui o problema do widget em uma grade que muda sua altura por vontade própria, e o sistema de grade deve ser elástico o suficiente para reagir ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
fonte
1
ótima resposta, mas teria se beneficiado de um exemplo mais simples. Basicamente, a resposta de whiteknight, mas com useLayoutEffectuma div real para amarrar o árbitro.
bot19
1

Usando com ganchos:

Essa resposta seria útil se sua dimensão de conteúdo fosse alterada após o carregamento.

onreadystatechange : ocorre quando o estado de carregamento dos dados pertencentes a um elemento ou documento HTML é alterado. O evento onreadystatechange é disparado em um documento HTML quando o estado de carregamento do conteúdo da página muda.

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

Eu estava tentando trabalhar com a incorporação de um player de vídeo do YouTube cujas dimensões podem mudar após o carregamento.

iamakshatjain
fonte
0

Aqui está outro caso você precise de um evento de redimensionamento de janela:

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

caneta de código

Gfast2
fonte
0

Uma solução alternativa, caso você queira recuperar o tamanho de um elemento React de forma síncrona, sem ter que renderizar visivelmente o elemento, você pode usar ReactDOMServer e DOMParser .

Eu uso essa função para obter a altura de um a minha lista de itens de representante quando usando reagir janela ( reagem-virtualizado ), em vez de ter de codificar o necessário itemSizesuporte para uma 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
fonte