ReactJS - Ottieni l'altezza di un elemento


121

Come posso ottenere l'altezza di un elemento dopo che React ha renderizzato quell'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')
);

RISULTATO

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

Sta calcolando l'altezza del contenitore prima del rendering (36px). Voglio ottenere l'altezza dopo il rendering. Il risultato corretto dovrebbe essere 18px in questo caso. jsfiddle


1
Questa non è una domanda di risposta, ma piuttosto una domanda Javascript e DOM. Dovresti cercare di capire quale evento DOM dovresti usare per trovare l'altezza finale del tuo elemento. Nel gestore eventi, è possibile utilizzare setStateper assegnare il valore di altezza a una variabile di stato.
mostruash

Risposte:


28

Guarda questo violino (effettivamente aggiornato il tuo)

È necessario agganciarsi a componentDidMountcui viene eseguito dopo il metodo di rendering. Lì, ottieni l'altezza effettiva dell'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>

15
Non corretto. vedere la risposta di Paul Vincent Beigang di seguito
ekatz

1
IMHO, un componente non dovrebbe conoscere il nome del suo contenitore
Andrés

156

Di seguito è riportato un esempio ES6 aggiornato utilizzando un rif .

Ricorda che dobbiamo usare un componente di classe React poiché dobbiamo accedere al metodo Lifecycle componentDidMount()perché possiamo determinare l'altezza di un elemento solo dopo che è stato renderizzato nel 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'))

Puoi trovare l'esempio in esecuzione qui: https://codepen.io/bassgang/pen/povzjKw


6
Funziona ma ricevo diversi errori di eslint per Airbnb styleguide: <div ref={ divElement => this.divElement = divElement}>{children}</div>lancia "[eslint] La funzione freccia non dovrebbe restituire l'assegnazione. (No-return-assign)" e this.setState({ height });lancia "[eslint] Non usare setState in componentDidMount (react / no- ha-mount-set-state)". Qualcuno ha una soluzione conforme alla styleguide?
Timo

7
Racchiudi il compito tra parentesi graffe in modo che si comporti come una funzione (piuttosto che come un'espressione), in questo modoref={ divElement => {this.divElement = divElement}}
teletypist

3
Questa potrebbe essere la risposta accettata :) Inoltre, se vuoi ottenere la dimensione dell'elemento dopo che l'elemento è stato aggiornato, puoi usare anche il metodo componentDidUpdate.
jjimenez

4
c'è un'alternativa alla chiamata this.setState () in componentDidMount con questo esempio?
danjones_mcr

3
Buona soluzione. Ma non funzionerà per progetti reattivi. Se l'altezza dell'intestazione era di 50 px per il desktop e l'utente riduce le dimensioni della finestra e ora l'altezza diventa 100 px, questa soluzione non prenderà l'altezza aggiornata. Devono aggiornare la pagina per ottenere gli effetti modificati.
TheCoder

97

Per coloro che sono interessati all'uso react hooks, questo potrebbe aiutarti a iniziare.

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

11
Grazie per la tua risposta, mi ha aiutato a iniziare. 2 domande però: (1) Per queste attività di misurazione del layout, dovremmo usare al useLayoutEffectposto di useEffect? I documenti sembrano indicare che dovremmo. Vedi la sezione "suggerimento" qui: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Per questi casi in cui abbiamo solo bisogno di un riferimento a un elemento figlio, dovremmo usare al useRefposto di createRef? I documenti sembrano indicare che useRefè più appropriato per questo caso d'uso. Vedi: reactjs.org/docs/hooks-reference.html#useref
Jason Frank

Ho appena implementato una soluzione che utilizza i due elementi che sto suggerendo. Sembrano funzionare bene, ma potresti conoscere alcuni aspetti negativi di queste scelte? Grazie per l'aiuto!
Jason Frank il

4
@ JasonFrank Buone domande. 1) Sia useEffect che useLayoutEffect verranno attivati ​​dopo il layout e la pittura. Tuttavia, useLayoutEffect si attiverà in modo sincrono prima del disegno successivo. Direi che se hai davvero bisogno di coerenza visiva, usa useLayoutEffect, altrimenti, useEffect dovrebbe essere sufficiente. 2) Hai ragione su questo! In un componente funzionale createRef creerà un nuovo riferimento ogni volta che il componente viene renderizzato. L'uso di useRef è l'opzione migliore. Aggiornerò la mia risposta. Grazie!
Sig. 14

Questo mi ha aiutato a capire come usare un ref con i ganci. Ho finito per andare con useLayoutEffectdopo aver letto gli altri commenti qui. Sembra funzionare bene. :)
GuiDoody

1
Stavo impostando le dimensioni del mio componente useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))e il mio IDE (WebStorm) mi ha suggerito questo: ESLint: React Hook useEffect contiene una chiamata a 'setDimensions'. Senza un elenco di dipendenze, ciò può portare a una catena infinita di aggiornamenti. Per risolvere questo problema, passare [] come secondo argomento a useEffect Hook. ([]useEffect
React

9

Vorresti anche usare ref sull'elemento invece di usarlo document.getElementById, è solo una cosa leggermente più robusta.


@doublejosh fondamentalmente hai ragione ma cosa fare se hai bisogno di confondere per esempio angularJse reactdurante la migrazione da uno all'altro? Ci sono casi in cui è davvero necessaria una manipolazione diretta del DOM
messerbill

2

La mia risposta del 2020 (o del 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>
    )
}

usa la tua immaginazione per ciò che manca qui (WidgetHead), reactstrapè qualcosa che puoi trovare su npm: sostituisci innerRefcon refun elemento dom legacy (ad esempio a <div>).

useEffect o useLayoutEffect

Si dice che l'ultimo sia sincrono per i cambiamenti

useLayoutEffect(o useEffect) secondo argomento

Il secondo argomento è un array e viene verificato prima di eseguire la funzione nel primo argomento.

ero solito

[me stesso.current, me stesso.corrente? me stesso.current.clientHeight: 0]

poiché me stesso.current è nullo prima del rendering, ed è una buona cosa non controllare, il secondo parametro alla fine myself.current.clientHeightè quello che voglio controllare per le modifiche.

cosa sto risolvendo qui (o cercando di risolvere)

Sto risolvendo qui il problema del widget su una griglia che cambia la sua altezza per propria volontà e il sistema della griglia dovrebbe essere abbastanza elastico per reagire ( https://github.com/STRML/react-grid-layout ).


1
ottima risposta, ma avrebbe beneficiato di un esempio più semplice. Fondamentalmente la risposta di whiteknight ma con useLayoutEffectun div effettivo a cui legare il riferimento.
bot19

1

Utilizzo con ganci:

Questa risposta sarebbe utile se la dimensione del contenuto cambia dopo il caricamento.

onreadystatechange : si verifica quando lo stato di caricamento dei dati che appartengono a un elemento o un documento HTML cambia. L'evento onreadystatechange viene generato su un documento HTML quando lo stato di caricamento del contenuto della pagina è cambiato.

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

Stavo cercando di lavorare con l'incorporamento di un lettore video di YouTube le cui dimensioni potrebbero cambiare dopo il caricamento.



0

Eccone un altro se hai bisogno di un evento di ridimensionamento della finestra:

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

penna a codice


0

Una soluzione alternativa, nel caso in cui desideri recuperare la dimensione di un elemento React in modo sincrono senza dover renderizzare visibilmente l'elemento, puoi usare ReactDOMServer e DOMParser .

Uso questa funzione per ottenere l'altezza di un renderer di elementi della mia lista quando si usa la finestra di reazione ( virtuale-reattiva ) invece di dover codificare come hardcoded il itemSizepuntello richiesto per 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
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.