ReactJS - получить высоту элемента

121

Как я могу получить высоту элемента после того, как 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')
);

РЕЗУЛЬТАТ

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

Он вычисляет высоту контейнера до рендеринга (36 пикселей). Я хочу получить высоту после рендера. В этом случае правильный результат должен быть 18 пикселей. jsfiddle

faia20
источник
1
Это не вопрос реакции, а вопрос Javascript и DOM. Вам следует попытаться выяснить, какое событие DOM следует использовать для определения окончательной высоты вашего элемента. В обработчике событий вы можете использовать setStateдля присвоения значения высоты переменной состояния.
Mostruash

Ответы:

28

См. Эту скрипку (на самом деле обновил ваш)

Вам нужно подключиться к тому, componentDidMountчто выполняется после метода рендеринга. Здесь вы получаете фактическую высоту элемента.

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
источник
16
не верно. см. ответ Пола Винсента Бейганга ниже
ekatz
1
IMHO, компонент не должен знать название своего контейнера
Andrés
157

Ниже приведен обновленный пример ES6 с исх .

Помните, что мы должны использовать компонент класса React, так как нам нужен доступ к методу жизненного цикла, componentDidMount()потому что мы можем определить высоту элемента только после того, как он будет отрисован в 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'))

Вы можете найти работающий пример здесь: https://codepen.io/bassgang/pen/povzjKw

Пол Винсент Бейган
источник
6
Это работает, но я получаю несколько ошибок eslint для руководства по стилю Airbnb: <div ref={ divElement => this.divElement = divElement}>{children}</div>выдает «[eslint] Стрелочная функция не должна возвращать присвоение. (No-return-assign)» и this.setState({ height });выдает «[eslint] Не используйте setState в componentDidMount (response / no- did-mount-set-state) ". У кого-нибудь есть решение, соответствующее руководству по стилю?
Timo
7
Оберните присвоение в фигурные скобки, чтобы оно ref={ divElement => {this.divElement = divElement}}
вело
3
Это может быть принятый ответ :) Кроме того, если вы хотите получить размер элемента после его обновления, вы также можете использовать этот метод componentDidUpdate.
jjimenez
4
есть ли альтернатива вызову this.setState () в componentDidMount в этом примере?
danjones_mcr
3
Хорошее решение. Но не подойдет для адаптивного дизайна. Если высота заголовка была 50 пикселей для рабочего стола, а пользователь уменьшает размер окна, а теперь высота становится 100 пикселей, это решение не будет принимать обновленную высоту. Им необходимо обновить страницу, чтобы получить измененные эффекты.
TheCoder
100

Для тех, кто заинтересован в использовании react hooks, это может помочь вам начать работу.

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>
  )
}
Мистер 14
источник
11
Спасибо за ответ - это помогло мне начать работу. Тем не менее, 2 вопроса: (1) Должны ли мы использовать useLayoutEffectвместо useEffect? Документы, кажется, указывают, что мы должны. См. Раздел «совет» здесь: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) В тех случаях, когда нам просто нужна ссылка на дочерний элемент, следует ли использовать useRefвместо createRef? Документы, похоже, указывают на то, что useRefон более подходит для этого варианта использования. См .: responsejs.org/docs/hooks-reference.html#useref
Джейсон Франк,
Я только что реализовал решение, в котором используются два предлагаемых мною элемента. Кажется, они работают хорошо, но, возможно, вы знаете о некоторых недостатках этих вариантов? Спасибо за вашу помощь!
Джейсон Франк
4
@JasonFrank Хорошие вопросы. 1) И useEffect, и useLayoutEffect будут запущены после макета и рисования. Однако useLayoutEffect будет запускаться синхронно перед следующей отрисовкой. Я бы сказал, что если вам действительно нужна визуальная согласованность, используйте useLayoutEffect, в противном случае useEffect должно хватить. 2) Вы правы! В функциональном компоненте createRef будет создавать новый ref каждый раз, когда компонент визуализируется. Использование useRef - лучший вариант. Я обновлю свой ответ. Спасибо!
Мистер 14,
Это помогло мне понять, как использовать реф с хуками. Я закончил, useLayoutEffectпрочитав другие комментарии здесь. Кажется, работает хорошо. :)
GuiDoody 01
1
Я устанавливал размеры своего компонента useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), и моя IDE (WebStorm) предложила мне следующее: ESLint: React Hook useEffect содержит вызов setDimensions. Без списка зависимостей это может привести к бесконечной цепочке обновлений. Чтобы исправить это, передайте [] в качестве второго аргумента хука useEffect. (react-hooks / excustive-deps) , так что передайте в []качестве второго аргумента вашuseEffect
Акшдип Сингх
9

Вы также можете использовать refs для элемента вместо использования document.getElementById, это просто немного более надежная вещь.

yoyodunno
источник
@doublejosh в основном вы правы, но что делать, если вам нужно смешать, например, angularJsи reactпри переходе с одного на другой? Бывают случаи, когда прямая манипуляция с DOM действительно необходима
messerbill
3

Мой ответ 2020 (или 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>
    )
}

позвольте использовать ваше воображение для того, чего здесь не хватает (WidgetHead), reactstrapэто то , что вы можете найти на npm: replace innerRefwith refдля устаревшего элемента dom (скажем, a <div>).

useEffect или useLayoutEffect

Последний считается синхронным для изменений

useLayoutEffect(или useEffect) второй аргумент

Второй аргумент - это массив, и он проверяется перед выполнением функции в первом аргументе.

я использовал

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

поскольку перед рендерингомself.current имеет значение null, и это хорошо, чтобы не проверять, второй параметр в конце myself.current.clientHeight- это то, что я хочу проверить на предмет изменений.

что я здесь решаю (или пытаюсь решить)

Я решаю здесь проблему виджетов в сетке, которые изменяют свою высоту по собственному желанию, и система сетки должна быть достаточно эластичной, чтобы реагировать ( https://github.com/STRML/react-grid-layout ).

Даниэле Кручиани
источник
1
отличный ответ, но был бы полезен более простой пример. В основном ответ whiteknight, но с useLayoutEffectфактическим div для привязки ссылки.
bot19
3

document.getElementById(...)Лучшее (актуальное) решение вместо использования - использовать перехватчик React useRef, который хранит ссылку на компонент / элемент, в сочетании с перехватчиком useEffect , который срабатывает при рендеринге компонента.

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

Использование с крючками:

Этот ответ будет полезен, если размер вашего контента изменится после загрузки.

onreadystatechange : возникает при изменении состояния загрузки данных, принадлежащих элементу или документу HTML. Событие onreadystatechange запускается в HTML-документе при изменении состояния загрузки содержимого страницы.

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

Я пытался работать со встроенным видеоплеером YouTube, размеры которого могут измениться после загрузки.

Ямакшатджайн
источник
0

Я нашел полезный пакет npm https://www.npmjs.com/package/element-resize-detector

Оптимизированный кросс-браузерный слушатель изменения размера элементов.

Можно использовать с компонентом React или функциональным компонентом (особенно полезно для перехватчиков реакции)

qmn1711
источник
0

Вот еще одно, если вам нужно событие изменения размера окна:

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

кодовая ручка

Gfast2
источник
0

Альтернативное решение, если вы хотите получить размер элемента React синхронно без необходимости визуализации элемента, вы можете использовать ReactDOMServer и DOMParser .

Я использую эту функцию, чтобы получить высоту средства визуализации элемента моего списка при использовании реактивного окна ( реактивно-виртуализированного ) вместо того, чтобы жестко кодировать требуемую itemSizeопору для 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
};
Темный, 1978
источник