ReactJS - Höhe eines Elements ermitteln

121

Wie kann ich die Höhe eines Elements ermitteln, nachdem React dieses Element gerendert hat?

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

ERGEBNIS

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

Es berechnet die Containerhöhe vor dem Rendern (36px). Ich möchte die Höhe nach dem Rendern erhalten. Das richtige Ergebnis sollte in diesem Fall 18px sein. jsfiddle

faia20
quelle
1
Dies ist keine Reaktionsfrage, sondern eine Javascript- und DOM-Frage. Sie sollten versuchen herauszufinden, welches DOM-Ereignis Sie verwenden sollten, um die endgültige Höhe Ihres Elements zu ermitteln. Im Ereignishandler können Sie setStateden Höhenwert einer Statusvariablen zuweisen.
Mostruash

Antworten:

29

Sehen Sie diese Geige (tatsächlich aktualisiert Ihre)

Sie müssen sich einbinden, in componentDidMountdas nach der Rendermethode ausgeführt wird. Dort erhalten Sie die tatsächliche Höhe des Elements.

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
quelle
15
nicht richtig. siehe Paul Vincent
Beigangs
1
IMHO, eine Komponente sollte den Namen ihres Containers nicht kennen
Andrés
154

Es folgt ein aktuelles ES6-Beispiel mit einer Referenz .

Denken Sie daran, dass wir eine React-Klassenkomponente verwenden müssen, da wir auf die Lifecycle-Methode zugreifen müssen, componentDidMount()da wir die Höhe eines Elements erst bestimmen können, nachdem es im DOM gerendert wurde.

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

Das laufende Beispiel finden Sie hier: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
quelle
6
Dies funktioniert, aber ich erhalte mehrere Eslint-Fehler für Airbnb Styleguide: <div ref={ divElement => this.divElement = divElement}>{children}</div>löst "[eslint] Arrow-Funktion sollte keine Zuweisung zurückgeben. (No-return- this.setState({ height });assign )" und wirft "[eslint] Verwenden Sie setState nicht in componentDidMount (react / no- did-mount-set-state) ". Hat jemand eine Styleguide-konforme Lösung?
Timo
7
Wickeln Sie die Aufgabe in geschweifte Klammern, damit sie sich wie eine Funktion (und nicht wie ein Ausdruck) verhältref={ divElement => {this.divElement = divElement}}
Teletypist
3
Dies sollte die akzeptierte Antwort sein :) Außerdem können Sie die Methode componentDidUpdateauch verwenden , wenn Sie die Größe des Elements nach der Aktualisierung des Elements erhalten möchten .
jjimenez
4
Gibt es in diesem Beispiel eine Alternative zum Aufrufen von this.setState () in componentDidMount?
danjones_mcr
3
Gute Lösung. Funktioniert aber nicht für reaktionsschnelle Designs. Wenn die Headerhöhe für den Desktop 50 Pixel betrug und der Benutzer die Fenstergröße verkleinert und die Höhe jetzt 100 Pixel beträgt, übernimmt diese Lösung nicht die aktualisierte Höhe. Sie müssen die Seite aktualisieren, um die geänderten Effekte zu erhalten.
TheCoder
94

Für diejenigen, die an der Verwendung interessiert sind react hooks, kann dies den Einstieg erleichtern.

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>
  )
}
Herr 14
quelle
11
Vielen Dank für Ihre Antwort - es hat mir geholfen, loszulegen. 2 Fragen: (1) Sollten wir für diese Layout-Messaufgaben useLayoutEffectanstelle von verwenden useEffect? Die Dokumente scheinen darauf hinzudeuten, dass wir sollten. Siehe den Abschnitt "Tipp" hier: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) In diesen Fällen, in denen wir nur einen Verweis auf ein untergeordnetes Element benötigen, sollten wir useRefstattdessen verwenden createRef? Die Dokumente scheinen darauf hinzuweisen, useRefdass dies für diesen Anwendungsfall besser geeignet ist. Siehe: reactjs.org/docs/hooks-reference.html#useref
Jason Frank
Ich habe gerade eine Lösung implementiert, die die beiden von mir vorgeschlagenen Elemente verwendet. Sie scheinen gut zu funktionieren, aber Sie kennen vielleicht einige Nachteile dieser Entscheidungen? Danke für Ihre Hilfe!
Jason Frank
4
@ JasonFrank Gute Fragen. 1) Sowohl useEffect als auch useLayoutEffect werden nach dem Layout und dem Malen ausgelöst. UseLayoutEffect wird jedoch vor dem nächsten Lack synchron ausgelöst. Ich würde sagen, wenn Sie wirklich visuelle Konsistenz benötigen, verwenden Sie useLayoutEffect, andernfalls sollte useEffect ausreichen. 2) Da hast du recht! In einer Funktionskomponente erstellt createRef jedes Mal, wenn die Komponente gerendert wird, eine neue Referenz. Die Verwendung von useRef ist die bessere Option. Ich werde meine Antwort aktualisieren. Vielen Dank!
Herr 14
Dies half mir zu verstehen, wie man einen Schiedsrichter mit Haken benutzt. useLayoutEffectNachdem ich die anderen Kommentare hier gelesen hatte , ging ich mit . Scheint gut zu funktionieren. :)
GuiDoody
1
Ich habe die Abmessungen meiner Komponente festgelegt useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), und meine IDE (WebStorm) hat mir Folgendes vorgeschlagen: ESLint: React Hook useEffect enthält einen Aufruf von 'setDimensions'. Ohne eine Liste von Abhängigkeiten kann dies zu einer unendlichen Kette von Aktualisierungen führen. Um dies zu beheben, übergeben Sie [] als zweites Argument an den useEffect-Hook. (React-Hooks / erschöpfende Deps) , also []als zweites Argument an IhreuseEffect
Akshdeep Singh
9

Sie möchten auch Refs für das Element verwenden, anstatt es zu verwenden document.getElementById. Es ist nur eine etwas robustere Sache.

yoyodunno
quelle
@doublejosh im Grunde haben Sie Recht, aber was tun, wenn Sie zum Beispiel verwechseln müssen angularJsund reactwährend Sie von einem zum anderen migrieren? Es gibt Fälle, in denen eine direkte DOM-Manipulation wirklich erforderlich ist
messerbill
2

Meine 2020 (oder 2019) Antwort

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

nutzen Sie Ihre Fantasie lassen , was hier (WidgetHead) fehlt, reactstrapist etwas , das Sie auf npm finden: ersetzen innerRefmit refeinem Vermächtnis dom Element (sagen a <div>).

useEffect oder useLayoutEffect

Der letzte soll für Änderungen synchron sein

useLayoutEffect(oder useEffect) zweites Argument

Das zweite Argument ist ein Array und wird überprüft, bevor die Funktion im ersten Argument ausgeführt wird.

ich benutzte

[Ich selbst. Aktuell, Ich. Aktuell? Ich. Aktuell. Kunde Höhe: 0]

Da mir.current vor dem Rendern null ist und dies eine gute Sache ist, die nicht überprüft werden sollte, myself.current.clientHeightmöchte ich den zweiten Parameter am Ende auf Änderungen überprüfen.

was ich hier löse (oder zu lösen versuche)

Ich löse hier das Problem des Widgets in einem Raster, das seine Höhe nach eigenem Willen ändert, und das Rastersystem sollte elastisch genug sein , um zu reagieren ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
quelle
1
tolle Antwort, hätte aber von einem einfacheren Beispiel profitiert. Grundsätzlich die Antwort von Whiteknight, aber mit useLayoutEffectund tatsächlichem Div, um den Schiedsrichter zu binden.
Bot19
1

Verwendung mit Haken:

Diese Antwort wäre hilfreich, wenn sich Ihre Inhaltsdimension nach dem Laden ändert.

onreadystatechange : Tritt auf, wenn sich der Ladezustand der Daten, die zu einem Element oder einem HTML-Dokument gehören, ändert. Das Ereignis onreadystatechange wird in einem HTML-Dokument ausgelöst, wenn sich der Ladezustand des Seiteninhalts geändert hat.

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

Ich habe versucht, mit einem eingebetteten YouTube-Videoplayer zu arbeiten, dessen Abmessungen sich nach dem Laden ändern können.

iamakshatjain
quelle
0

Ich fand nützliches npm-Paket https://www.npmjs.com/package/element-resize-detector

Ein optimierter browserübergreifender Listener für die Größenänderung von Elementen.

Kann mit React-Komponente oder Funktionskomponente verwendet werden (besonders nützlich für React-Hooks)

qmn1711
quelle
0

Hier ist ein weiteres, wenn Sie ein Ereignis zum Ändern der Fenstergröße benötigen:

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

Codestift

Gfast2
quelle
0

Eine alternative Lösung: Wenn Sie die Größe eines React-Elements synchron abrufen möchten, ohne das Element sichtbar rendern zu müssen, können Sie ReactDOMServer und DOMParser verwenden .

Ich verwende diese Funktion, um die Höhe eines Renderers für meine Listenelemente zu ermitteln, wenn ich das React -Fenster ( React-Virtualized ) verwende, anstatt die erforderliche itemSizeRequisite für eine FixedSizeList fest zu codieren .

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
quelle