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

faia20
źródło
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>
Andreyco
źródło
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

Paul Vincent Beigang
źródło
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>
  )
}
Pan 14
źródło
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.

yoyodunno
źródło
@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 ).

Daniele Cruciani
źródło
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>
  )
}
charri
źródło
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.

iamakshatjain
źródło
0

Znalazłem przydatny pakiet npm https://www.npmjs.com/package/element-resize-detector

Zoptymalizowany detektor zmiany rozmiaru elementów w różnych przeglądarkach.

Można go używać z komponentem React lub komponentem funkcjonalnym (szczególnie przydatne w przypadku haków reakcyjnych)

qmn1711
źródło
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

Gfast2
źródło
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
};
TheDarkIn1978
źródło