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


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>

15
неправильно. дивіться відповідь Пола Вінсента Бейґана нижче
ekatz

1
IMHO, компонент не повинен знати назву свого контейнера
Andrés

156

Далі наведено сучасний приклад 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 styleguide: <div ref={ divElement => this.divElement = divElement}>{children}</div>кидає "[eslint] Функція стрілки не повинна повертати призначення. (No-return-присвоєння)" та this.setState({ height });кидає "[eslint] Не використовуйте setState в компонентDidMount (реагуйте / ні- did-mount-set-state) ". Хтось отримав рішення, що відповідає стилю?
Тимо

7
Загорніть завдання в дужки, щоб він поводився як функція (а не вираз), як цеref={ divElement => {this.divElement = divElement}}
телетипіст

3
Це може бути прийнятою відповіддю :) Крім того, якщо ви хочете отримати розмір елемента після оновлення елемента, ви можете також скористатися методом componentDidUpdate.
jjimenez

4
Чи є альтернатива викликати this.setState () в компонентіDidMount за допомогою цього прикладу?
danjones_mcr

3
Гарне рішення. Але не реагуватиме на чуйні дизайни. Якщо висота заголовка для робочого столу становила 50 пікселів, а користувач зменшує розмір вікна, а тепер висота стає 100 пікселів, це рішення не буде приймати оновлену висоту. Їм потрібно оновити сторінку, щоб отримати змінені ефекти.
TheCoder

96

Для тих, хто зацікавлений у використанні 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>
  )
}

11
Дякую за вашу відповідь - це допомогло мені почати. Хоча два питання: (1) Для цих завдань з вимірювання макета нам слід використовувати useLayoutEffectзамість useEffect? Документи, схоже, вказують на те, що ми повинні. Дивіться розділ "підказки" тут: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Для цих випадків, коли нам просто потрібна посилання на дочірній елемент, чи слід використовувати його useRefзамість createRef? Документи, схоже, вказують на те, що useRefє більш підходящим для цього випадку використання. Дивіться: reactjs.org/docs/hooks-reference.html#useref
Джейсон Френк

Я щойно реалізував рішення, яке використовує два пункти, які я пропоную. Вони, здається, працюють добре, але ви можете знати деякі недоліки цих варіантів? Спасибі за вашу допомогу!
Джейсон Френк

4
@JasonFrank Хороші запитання. 1) І useEffect, і useLayoutEffect буде вимкнено після макета та фарби. Однак useLayoutEffect буде запускатись синхронно перед наступною фарбою. Я б сказав, якщо вам дійсно потрібна візуальна консистенція, тоді використовуйте useLayoutEffect, інакше useEffect має бути достатньо. 2) Ви маєте рацію з цього приводу! У функціональному компоненті createRef створюватиме новий перегляд кожного разу, коли компонент надається. Використання UseRef - кращий варіант. Я оновлю свою відповідь. Дякую!
Містер 14

Це допомогло мені зрозуміти, як користуватися реф з гачками. Я закінчила свою роботу useLayoutEffectпісля того, як прочитала тут інші коментарі. Здається, добре працює. :)
GuiDoody

1
Я встановлював розміри свого компонента useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), і мій IDE (WebStorm) запропонував мені це: ESLint: React Hook useEffect містить виклик "setDimensions". Без переліку залежностей це може призвести до нескінченного ланцюжка оновлень. Щоб виправити це, передайте [] як другий аргумент useEffect Hook. (реагувати-гачки / вичерпні депи) , тому передайте []як другий аргумент своємуuseEffect
Akshdeep Singh

9

Ви також хочете використовувати refs для елемента, а не використовувати document.getElementById, це просто трохи надійніша річ.


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

2

Відповідь мого 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є те , що ви можете знайти на НПМ: замінити innerRefз refна спадщину йот елемент (скажімо <div>).

useEffect або useLayoutEffect

Остання, як кажуть, є синхронною для змін

useLayoutEffect(або useEffect) другий аргумент

Другий аргумент - це масив, і він перевіряється перед виконанням функції першого аргументу.

я використав

[себе.поточний, сам.поточний? сам.струмент.клиентВисота: 0]

оскільки сам.current є нульовим перед рендерінгом, і це добре не перевіряти, другий параметр в кінці myself.current.clientHeight- це те, що я хочу перевірити на зміни.

що я тут вирішую (або намагаюся вирішити)

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


1
чудова відповідь, але виграв би простіший приклад. В основному відповідь білого вечора, але з useLayoutEffectі власне дівом, щоб прив'язати відповідь.
bot19

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 або функціональним компонентом (особливо корисно для гачків, що реагують)


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

ручка з кодом


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
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.