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像素)。我想获得渲染后的高度。在这种情况下,正确的结果应为18px。jsfiddle


1
这不是一个反应性问题,而是一个Javascript和DOM问题。您应该尝试确定应该使用哪个DOM事件来查找元素的最终高度。在事件处理程序中,可以用于setState将高度值分配给状态变量。
mostruash '16

Answers:


28

看到这个小提琴(实际上更新了您的)

您需要钩住componentDidMount在render方法之后运行的对象。在那里,您可以获得元素的实际高度。

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
不正确。请参阅下面的Paul Vincent
Beigang

1
恕我直言,组件不应该知道它的容器的名称
安德烈斯

156

以下是使用ref的最新ES6示例。

请记住,我们必须使用React类组件,因为我们需要访问Lifecycle方法,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
这可行,但是我在Airbnb样式指南中遇到了一些eslint错误:<div ref={ divElement => this.divElement = divElement}>{children}</div>抛出“ [eslint]箭头函数不应返回赋值。(no-return-assign)”,并this.setState({ height });抛出“ [eslint]请勿在componentDidMount中使用setState(反应/否- did-mount-set-state)”。有人得到了符合styleguide的解决方案吗?
蒂莫

7
将作业用大括号括起来,使其表现为函数(而不是表达式),就像这样ref={ divElement => {this.divElement = divElement}}
电传打字员

3
这是可以接受的答案:)此外,如果要在元素更新后获取元素的大小,也可以使用该方法componentDidUpdate
jjimenez

4
在此示例中,除了在componentDidMount中调用this.setState()之外,还有其他方法吗?
danjones_mcr

3
好的解决方案。但是不适用于响应式设计。如果桌面的标题高度为50px,并且用户缩小窗口大小,而现在高度变为100px,则此解决方案将不采用更新的高度。他们需要刷新页面以获得更改的效果。
TheCoder

97

对于那些有兴趣使用的人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
Jason Frank

我刚刚实现了一个使用我建议的两项的解决方案。它们似乎运作良好,但是您可能会知道这些选择的不利之处?谢谢你的帮助!
詹森·弗兰克

4
@JasonFrank好问题。1)useEffect和useLayoutEffect都将在布局和绘制后被触发。但是,useLayoutEffect将在下一次绘制之前同步触发。我想说的是,如果您确实需要视觉一致性,请使用useLayoutEffect,否则,useEffect就足够了。2)你是对的!在功能组件中,每次渲染组件时,createRef都会创建一个新的ref。使用useRef是更好的选择。我将更新我的答案。谢谢!

这有助于我理解如何使用带钩子的ref。useLayoutEffect在阅读了这里的其他评论后,我最终选择了。似乎运作良好。:)
GuiDoody '19

1
我正在设置组件的尺寸useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})),而我的IDE(WebStorm)向我提出了以下建议:ESLint:React Hook useEffect包含对“ setDimensions”的调用。没有依赖关系列表,这可能导致无限的更新链。要解决此问题,请将[]作为第二个参数传递给useEffect Hook。(react-hooks / exhaustive-deps),因此将[]第二个论点传递给您的useEffect
Akshdeep Singh,

9

您还希望在元素上使用refs而不是使用document.getElementById,这只是稍微健壮的事情。


@doublejosh基本上您是对的,但是如果您需要例如进行混合angularJsreact从一个迁移到另一个时该怎么办?在有些情况下确实需要直接的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是你可以找到关于故宫:更换innerRefref的传统的DOM元素(说<div>)。

useEffect或useLayoutEffect

据说最后一个是同步变化的

useLayoutEffect(或useEffect)第二个参数

第二个参数是一个数组,在执行第一个参数中的函数之前会对其进行检查。

我用了

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

因为我的电流在渲染前为null,所以最好不要检查,最后的第二个参数myself.current.clientHeight是我要检查的更改。

我在这里解决(或尝试解决)的问题

我在这里解决网格上的小部件的问题,该部件会根据自己的意愿改变其高度,并且网格系统应具有足够的弹性以做出反应(https://github.com/STRML/react-grid-layout)。


1
很好的答案,但可以从更简单的示例中受益。基本上是whiteknight的答案,但与useLayoutEffect实际div相联系。
bot19

1

与挂钩一起使用:

如果您的内容尺寸在加载后发生更改,此答案将很有帮助。

onreadystatechange:当属于元素或HTML文档的数据的加载状态更改时发生。页面内容的加载状态已更改时,将在HTML文档上触发onreadystatechange事件。

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

我正在尝试使用嵌入的youtube视频播放器,加载后尺寸可能会发生变化。



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元素的大小而不必显式呈现该元素,则可以使用ReactDOMServerDOMParser

在使用react-windowreact-virtualized)时,我可以使用此功能来获取列表项渲染器的高度,而不必对FixedSizeList进行必要的itemSize道具硬编码。

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
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.