ReactJS - Uzyskaj wysokość elementu


121

Jak mogę uzyskać wysokość elementu po wyrenderowaniu tego elementu przez React?

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

WYNIK

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

Oblicza wysokość kontenera przed renderowaniem (36px). Chcę uzyskać wysokość po renderowaniu. W tym przypadku prawidłowy wynik powinien wynosić 18 pikseli. jsfiddle


1
To nie jest pytanie na reakcję, ale raczej pytanie o Javascript i DOM. Powinieneś spróbować dowiedzieć się, którego zdarzenia DOM powinieneś użyć, aby znaleźć ostateczną wysokość elementu. W procedurze obsługi zdarzeń można użyć setStatedo przypisania wartości wysokości do zmiennej stanu.
— mostruash

Odpowiedzi:


28

Zobacz te skrzypce (faktycznie zaktualizowałem twoje)

Musisz podłączyć się do componentDidMountktórego jest uruchamiany po metodzie renderowania. Tam otrzymasz aktualną wysokość elementu.

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>

16
niepoprawne. zobacz odpowiedź Paula Vincenta
— Beiganga

1
IMHO, komponent nie powinien znać nazwy swojego pojemnika
— Andrés

157

Poniżej znajduje się aktualny przykład ES6 wykorzystujący ref .

Pamiętaj, że musimy użyć komponentu klasy React, ponieważ potrzebujemy dostępu do metody Lifecycle, componentDidMount()ponieważ możemy określić wysokość elementu dopiero po wyrenderowaniu go w 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'))

Przykład działania można znaleźć tutaj: https://codepen.io/bassgang/pen/povzjKw


6
To działa, ale otrzymuję kilka błędów eslint dla przewodnika stylów Airbnb: <div ref={ divElement => this.divElement = divElement}>{children}</div>zgłasza „[eslint] Funkcja strzałki nie powinna zwracać przypisania. (No-return-assign)” i this.setState({ height });zgłasza „[eslint] Nie używaj setState w komponencie componentDidMount (reaguj / nie- did-mount-set-state) ”. Czy ktoś ma rozwiązanie zgodne z przewodnikiem stylistycznym?
— Timo,

7
Owiń przypisanie w nawiasy klamrowe, aby zachowywało się jak funkcja (a nie wyrażenie), w ten sposóbref={ divElement => {this.divElement = divElement}}
— teletypist

3
To byłaby akceptowana odpowiedź :) Poza tym, jeśli chcesz uzyskać rozmiar elementu po aktualizacji elementu, możesz również użyć metody componentDidUpdate.
— jjimenez

4
czy istnieje alternatywa dla wywołania this.setState () w componentDidMount w tym przykładzie?
— danjones_mcr

3
Dobre rozwiązanie. Ale nie zadziała w przypadku responsywnych projektów. Jeśli wysokość nagłówka dla komputerów stacjonarnych wynosiła 50 pikseli, a użytkownik zmniejszy rozmiar okna, a teraz wysokość zmieni się na 100 pikseli, to rozwiązanie nie przyjmie zaktualizowanej wysokości. Muszą odświeżyć stronę, aby uzyskać zmienione efekty.
— TheCoder

100

Dla tych, którzy są zainteresowani użyciem react hooks, może to pomóc w rozpoczęciu.

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
Dziękuję za odpowiedź - pomogło mi to zacząć. Jednak dwa pytania: (1) Czy w przypadku tych zadań pomiaru układu powinniśmy używać useLayoutEffectzamiast useEffect? Dokumenty wydają się wskazywać, że powinniśmy. Zapoznaj się z sekcją „ porady ” tutaj: Reactjs.org/docs/hooks-effect.html#detailed-explanation (2) W tych przypadkach, w których potrzebujemy tylko odniesienia do elementu podrzędnego, czy powinniśmy użyć useRefzamiast createRef? Dokumentacja wydaje się wskazywać, że useRefjest bardziej odpowiedni dla tego przypadku użycia. Zobacz: responsjs.org/docs/hooks-reference.html#useref
— Jason Frank

Właśnie wdrożyłem rozwiązanie, które wykorzystuje dwa sugerowane przeze mnie elementy. Wydaje się, że działają dobrze, ale może znasz jakieś wady tych wyborów? Dzięki za pomoc!
— Jason Frank

4
@JasonFrank Dobre pytania. 1) Zarówno useEffect, jak i useLayoutEffect zostaną uruchomione po layoucie i pomalowaniu. Jednak metoda useLayoutEffect będzie uruchamiana synchronicznie przed kolejną farbą. Powiedziałbym, że jeśli naprawdę potrzebujesz spójności wizualnej, użyj useLayoutEffect, w przeciwnym razie useEffect powinno wystarczyć. 2) Masz rację! W komponencie funkcjonalnym createRef utworzy nowy odnośnik za każdym razem, gdy komponent jest renderowany. Korzystanie z useRef jest lepszą opcją. Zaktualizuję odpowiedź. Dzięki!
— Pan 14

Pomogło mi to zrozumieć, jak używać ref z hakami. Skończyło się na tym, że useLayoutEffectprzeczytałem inne komentarze tutaj. Wydaje się, że działa dobrze. :)
— GuiDoody

1
useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))Ustawiałem wymiary mojego komponentu , a moje IDE (WebStorm) zasugerowało mi to: ESLint: React Hook useEffect zawiera wywołanie „setDimensions”. Bez listy zależności może to prowadzić do nieskończonego łańcucha aktualizacji. Aby to naprawić, przekaż [] jako drugi argument do hooka useEffect. (reaguj-haki / wyczerpujące-deps) , więc przekaż []jako drugi argument do swojegouseEffect
— Akshdeep Singh

9

Chciałbyś również użyć referencji na elemencie zamiast używać document.getElementById, jest to tylko trochę bardziej solidna rzecz.


@doublejosh w zasadzie masz rację, ale co zrobić, jeśli musisz na przykład pomieszać angularJsi reactpodczas migracji z jednego do drugiego? Są przypadki, w których bezpośrednia manipulacja DOM jest naprawdę potrzebna
— messerbill

3

Moja odpowiedź z 2020 (lub 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>
    )
}

niech wykorzystać swoją wyobraźnię do czego brakuje tutaj (WidgetHead), reactstrapjest czymś, co można znaleźć na KMP zamienić innerRefze refdla elementu dziedzictwo dom (powiedzmy <div>).

useEffect lub useLayoutEffect

Mówi się, że to ostatnie jest synchroniczne dla zmian

useLayoutEffect(lub useEffect) drugi argument

Drugi argument jest tablicą i jest sprawdzany przed wykonaniem funkcji w pierwszym argumencie.

użyłem

[yourself.current, yourself.current? yourself.current.clientHeight: 0]

Ponieważ self.current ma wartość null przed renderowaniem, a tego dobrze nie sprawdzać, drugi parametr na końcu myself.current.clientHeightjest tym, co chcę sprawdzić pod kątem zmian.

co tutaj rozwiązuję (lub próbuję rozwiązać)

Rozwiązuję tutaj problem widżetu na siatce, który z własnej woli zmienia swoją wysokość, a układ siatki powinien być na tyle elastyczny, aby zareagować ( https://github.com/STRML/react-grid-layout ).


1
świetna odpowiedź, ale skorzystałby na prostszym przykładzie. Zasadniczo odpowiedź whiteknight, ale z useLayoutEffecti rzeczywistym divem do powiązania ref.
— bot19

2

Zamiast używać document.getElementById(...), lepszym (aktualnym) rozwiązaniem jest użycie zaczepu React useRef, który przechowuje odniesienie do komponentu / elementu, w połączeniu z punktem zaczepienia useEffect , który odpala przy renderowaniu składników.

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

1

Używanie z haczykami:

Ta odpowiedź byłaby pomocna, gdyby wymiar treści zmienił się po załadowaniu.

onreadystatechange : występuje, gdy stan ładowania danych należących do elementu lub dokumentu HTML ulegnie zmianie. Zdarzenie onreadystatechange jest wywoływane w dokumencie HTML, gdy zmienił się stan ładowania zawartości strony.

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

Próbowałem pracować z osadzaniem odtwarzacza wideo youtube, którego wymiary mogą się zmieniać po załadowaniu.



0

Oto kolejny, jeśli potrzebujesz zdarzenia zmiany rozmiaru okna:

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

kod pióra


0

Alternatywne rozwiązanie, w przypadku gdy chcesz pobrać rozmiar elementu React synchronicznie bez konieczności widocznego renderowania elementu, możesz użyć ReactDOMServer i DOMParser .

Używam tej funkcji, aby uzyskać wysokość renderera pozycji mojej listy podczas korzystania z okna reagowania ( zwirtualizowane reagowanie ), zamiast konieczności kodowania wymaganej właściwości itemSizedla 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
};
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.