【问题标题】:ReactJS - Get Height of an elementReactJS - 获取元素的高度
【发布时间】:2016-05-11 06:22:43
【问题描述】:

如何在 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

它在渲染之前计算容器高度(36px)。我想在渲染后获得高度。在这种情况下,正确的结果应该是 18px。 jsfiddle

【问题讨论】:

  • 这不是一个反应问题,而是一个 Javascript 和 DOM 问题。您应该尝试找出应该使用哪个 DOM 事件来找到元素的最终高度。在事件处理程序中,您可以使用setState 将高度值分配给状态变量。
  • 现在,我强烈建议只使用 react-use 的 useMeasure 挂钩来代替。

标签: javascript reactjs


【解决方案1】:

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

请记住,我们必须使用React class component,因为我们需要访问生命周期方法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

【讨论】:

  • 这行得通,但我收到了 Airbnb 样式指南的几个 eslint 错误:&lt;div ref={ divElement =&gt; this.divElement = divElement}&gt;{children}&lt;/div&gt; throws "[eslint] 箭头函数不应返回赋值。(no-return-assign)" 和 this.setState({ height }); throws " [eslint] 不要在 componentDidMount (react/no-did-mount-set-state) 中使用 setState”。有人有符合样式指南的解决方案吗?
  • 将赋值用大括号括起来,使其表现为函数(而不是表达式),例如ref={ divElement =&gt; {this.divElement = divElement}}
  • 这应该是公认的答案:) 另外,如果要在元素更新后获取元素的大小,也可以使用componentDidUpdate 方法。
  • 在这个例子中,在 componentDidMount 中调用 this.setState() 是否有替代方法?
  • 很好的解决方案。但不适用于响应式设计。如果桌面的标题高度为 50 像素,并且用户缩小了窗口大小,现在高度变为 100 像素,则此解决方案不会采用更新后的高度。他们需要刷新页面才能获得更改后的效果。
【解决方案2】:

对于那些有兴趣使用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>
  )
}

【讨论】:

  • 感谢您的回答 - 它帮助我入门。但是有两个问题:(1) 对于这些布局测量任务,我们应该使用useLayoutEffect 而不是useEffect?文档似乎表明我们应该这样做。请参阅此处的“提示”部分:reactjs.org/docs/hooks-effect.html#detailed-explanation (2) 对于这些我们只需要引用子元素的情况,我们是否应该使用useRef 而不是createRef?文档似乎表明useRef 更适合此用例。见:reactjs.org/docs/hooks-reference.html#useref
  • 我刚刚实现了一个使用我建议的两个项目的解决方案。它们似乎运作良好,但您可能知道这些选择的一些缺点?感谢您的帮助!
  • @JasonFrank 好问题。 1) useEffect 和 useLayoutEffect 都会在布局和绘制之后触发。但是,useLayoutEffect 将在下一次绘制之前同步触发。我想说如果你真的需要视觉一致性,那么使用 useLayoutEffect,否则,useEffect 就足够了。 2)你是对的!在功能组件中,createRef 将在每次渲染组件时创建一个新的 ref。使用 useRef 是更好的选择。我会更新我的答案。谢谢!
  • 我正在设置组件useEffect(() =&gt; setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})) 的尺寸,而我的 IDE (WebStorm) 建议我这样做:ESLint: React Hook useEffect 包含对“setDimensions”的调用。如果没有依赖项列表,这可能会导致无限的更新链。要解决此问题,请将 [] 作为第二个参数传递给 useEffect Hook。 (react-hooks/exhaustive-deps),所以将[] 作为第二个参数传递给useEffect
  • @faia20 您可能想选择这个更现代的答案。
【解决方案3】:

thisfiddle(实际上更新了你的)

您需要连接到在渲染方法之后运行的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>

【讨论】:

  • 不正确。请参阅下面的 Paul Vincent Beigang 的回答
  • 恕我直言,组件不应该知道其容器的名称
  • 这是错误的!不是一个好习惯。查看其他建议的解决方案。
【解决方案4】:

除了使用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(elementRef.current.clientHeight);
  }, []); //empty dependency array so it only runs once at render

  return (
    <div ref={elementRef}>
      {height}
    </div>
  )
}

【讨论】:

    【解决方案5】:

    您还想在元素上使用 refs 而不是使用document.getElementById,这只是一个更强大的东西。

    【讨论】:

    • @doublejosh 基本上你是对的,但是如果你在从一个迁移到另一个时需要混淆例如 angularJsreact 怎么办?在某些情况下,确实需要直接的 DOM 操作
    【解决方案6】:

    它可能显示为零。 setTimeout 有助于获取正确的值并更新状态。

    import React, { useState, useEffect, useRef } from 'react'
        
        export default () => {
          const [height, setHeight] = useState(0)
          const ref= useRef(null)
        
          useEffect(() => {
           if(elemRef.current.clientHeight){
             setTimeout(() => {
               setHeight(ref.current.clientHeight) 
             }, 1000)
           }
          })
        
          return (
            <div ref={ref}>
              {height}
            </div>
          )
        }
    

    【讨论】:

    • 还有另一个使用onreadystatechange 的解决方案,它也有效。我有一个 &lt;canvas&gt; 元素的引用,我试图获得正确的高度,setTimeout(()=&gt;{}, 0)document.onreadystatechange(()=&gt;{}) 都帮助获得了正确的高度。
    【解决方案7】:

    我 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 上找到的东西:将 innerRef 替换为 ref 以获得旧版 dom 元素(例如 &lt;div&gt;)。

    useEffect 或 useLayoutEffect

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

    useLayoutEffect(或useEffect)第二个参数

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

    我用过

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

    因为myself.current在渲染前为null,不检查是好事,最后第二个参数myself.current.clientHeight是我要检查的变化。

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

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

    【讨论】:

    • 很好的答案,但会从更简单的例子中受益。基本上是 whiteknight 的答案,但使用 useLayoutEffect 和实际的 div 将 ref 绑定到。
    【解决方案8】:

    与钩子一起使用:

    如果您的内容尺寸在加载后发生变化,此答案会很有帮助。

    onreadystatechange :当属于某个元素或 HTML 文档的数据的加载状态发生变化时发生。当页面内容的加载状态发生变化时,会在 HTML 文档上触发 onreadystatechange 事件。

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

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

    【讨论】:

    • 这个答案和提到的setTimeout 的使用被低估了。在我看来setTimeout(()=&gt;{}, 0)document.onreadstatechange = () =&gt; {} 具有相同的效果。无论哪种方式,在尝试将使用 Unity 构建的 webgl 游戏嵌入网页时,我都需要使用其中任何一种来从 getBoundingRect()clientWidthoffsetHeight 等获得正确的宽度/高度
    • @kimbaudi 我也看到使用 setTimeout 方法测量的宽度错误,但似乎只是偶尔发生。对我来说,到目前为止唯一稳定的方法是使用 onreadystatechange
    【解决方案9】:

    如果您需要调整窗口大小事件,这里还有一个:

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

    code pen

    【讨论】:

    • 很好的答案,这就是我要找的东西
    【解决方案10】:

    另一种解决方案,如果您想同步检索 React 元素的大小而不必显式渲染该元素,您可以使用 ReactDOMServerDOMParser强>。

    在使用 react-window (react-virtualized) 时,我使用此函数来获取列表项渲染器的高度,而不必硬编码所需的 @987654326 @prop 用于 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
    };
    

    【讨论】:

    • 您的解决方案太棒了!它真的救了我! :)
    【解决方案11】:

    我发现 React 钩子的其他答案在调整大小时没有正确更新。

    四处搜索后,我找到了this blog post,它提供了一个可以观察调整大小事件的有效 React 钩子:

    TL;DR 在这里:

    npm install --save resize-observer-polyfill

    // useResizeObserver.js
    import { useEffect, useRef } from 'react';
    import PropTypes from 'prop-types';
    import ResizeObserver from 'resize-observer-polyfill';
    
    const useObserver = ({ callback, element }) => {
    
      const current = element && element.current;
    
      const observer = useRef(null);
    
      useEffect(() => {
          // if we are already observing old element
          if (observer && observer.current && current) {
            observer.current.unobserve(current);
          }
          const resizeObserverOrPolyfill =  ResizeObserver;
          observer.current = new resizeObserverOrPolyfill(callback);
          observe();
    
          return () => {
            if (observer && observer.current && element &&
               element.current) {
              observer.current.unobserve(element.current);
            }
          };
      }, [current]);
    
      const observe = () => {
        if (element && element.current && observer.current) {
          observer.current.observe(element.current);
        }
      };
    
    };
    
    useObserver.propTypes = {
      element: PropTypes.object,
      callback: PropTypes.func,
    };
    
    export default useObserver;
    

    然后是组件中的示例用法:

    // shape.js
    import React, { useEffect, useState, useRef } from 'react';
    import useResizeObserver from 'path/to/useResizeObserver.js';
    
    const Shape = () => {
      const [height, setHeight] = useState(0);
      const svgRef = useRef(null);
    
      const doHeightAdjustment = () => {
        setHeight(svgRef.current.clientHeight);
      };
    
      useResizeObserver({callback: doHeightAdjustment, element: svgRef});
    
      return (
        <div ref={svgRef} style={{ height: '100vh' }}>
          {height}
        </div>
      );
    };
    
    export default Shape;
    
    

    【讨论】:

      【解决方案12】:

      您也可以使用getBoundingClientRect() 来获取高度、宽度。

      const [width, setWidth] = useState(0);
      
      useEffect(() => {
          const element = document.getElementById('element-id');
          if (element) {
            setWidth(element.getBoundingClientRect().width); // or height
          }
        }, []);
      

      【讨论】:

        【解决方案13】:

        使用useMeasure 作为自定义钩子(Typescript、SSR、钩子):

        import { useEffect, useRef, useState } from 'react';
        
        interface ContainerSize {
          width: number;
          height: number;
        }
        
        type UseMeasureArgs = () => {
          ref: React.RefObject<HTMLDivElement>;
          size: ContainerSize;
          windowSize: ContainerSize;
        };
        
        const initSize: ContainerSize = { width: 0, height: 0 };
        
        const useMeasure: UseMeasureArgs = () => {
          const ref = useRef<HTMLDivElement>(null);
          const [size, setSize] = useState<ContainerSize>(initSize);
          const [windowSize, setWindowSize] = useState<ContainerSize>(initSize);
        
          useEffect(() => {
            if (ref.current) {
              setSize({ width: ref.current.offsetWidth, height: ref.current.offsetHeight });
            }
            if (typeof window !== 'undefined') {
              setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight,
              });
            }
          }, []);
        
          return { ref, size, windowSize };
        };
        
        export default useMeasure;
        
        

        【讨论】:

          【解决方案14】:

          我找到了有用的 npm 包https://www.npmjs.com/package/element-resize-detector

          优化的跨浏览器调整元素大小监听器。

          可以与 React 组件或功能组件一起使用(特别适用于 react hooks)

          【讨论】:

            【解决方案15】:

            这是一个很好的可重复使用的钩子,从 https://swizec.com/blog/usedimensions-a-react-hook-to-measure-dom-nodes 修改而来:

            import { useState, useCallback, useEffect } from 'react';
            
            function getDimensionObject(node) {
              const rect = node.getBoundingClientRect();
            
              return {
                width: rect.width,
                height: rect.height,
                top: 'x' in rect ? rect.x : rect.top,
                left: 'y' in rect ? rect.y : rect.left,
                x: 'x' in rect ? rect.x : rect.left,
                y: 'y' in rect ? rect.y : rect.top,
                right: rect.right,
                bottom: rect.bottom
              };
            }
            
            export function useDimensions(data = null, liveMeasure = true) {
              const [dimensions, setDimensions] = useState({});
              const [node, setNode] = useState(null);
            
              const ref = useCallback(node => {
                setNode(node);
              }, []);
            
              useEffect(() => {
                if (node) {
                  const measure = () =>
                    window.requestAnimationFrame(() =>
                      setDimensions(getDimensionObject(node))
                    );
                  measure();
            
                  if (liveMeasure) {
                    window.addEventListener('resize', measure);
                    window.addEventListener('scroll', measure);
            
                    return () => {
                      window.removeEventListener('resize', measure);
                      window.removeEventListener('scroll', measure);
                    };
                  }
                }
              }, [node, data]);
            
              return [ref, dimensions, node];
            }
            

            实施:

            import { useDimensions } from '../hooks';
            
            // Include data if you want updated dimensions based on a change.
            const MyComponent = ({ data }) => {
              const [
                ref,
                { height, width, top, left, x, y, right, bottom }
              ] = useDimensions(data);
            
              console.log({ height, width, top, left, x, y, right, bottom });
            
              return (
                <div ref={ref}>
                  {data.map(d => (
                    <h2>{d.title}</h2>
                  ))}
                </div>
              );
            };
            
            

            【讨论】:

              猜你喜欢
              • 2021-06-16
              • 2016-04-27
              • 1970-01-01
              • 2014-09-14
              • 2011-08-05
              • 2013-08-11
              • 2014-06-08
              • 2012-11-06
              • 2012-05-01
              相关资源
              最近更新 更多