ChatGPT解决这个技术问题 Extra ChatGPT

ReactJS - 获取元素的高度

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>

反应JS

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 钩子来代替。

P
Paul Vincent Beigang

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

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


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

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

感谢您的回答 - 它帮助我入门。但是有 2 个问题:(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(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})) 的尺寸,我的 IDE (WebStorm) 建议我这样做:ESLint: React Hook useEffect 包含对“setDimensions”的调用。如果没有依赖项列表,这可能会导致无限的更新链。要解决此问题,请将 [] 作为第二个参数传递给 useEffect Hook。 (react-hooks/exhaustive-deps),所以将 [] 作为第二个参数传递给您的 useEffect
@faia20 您可能想要选择这个更现代的答案。
R
RobinJ

请参阅 this 小提琴(实际上已更新您的)

您需要连接到在渲染方法之后运行的 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 的回答
恕我直言,组件不应该知道其容器的名称
这是错误的!不是一个好习惯。请参阅其他建议的解决方案。
D
David Leuliette

除了使用 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>
  )
}

D
Devid Farinelli

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


@doublejosh 基本上你是对的,但是如果你在从一个迁移到另一个时需要混合例如 angularJsreact 怎么办?在某些情况下,确实需要直接的 DOM 操作
S
Shuhad zaman

它可能显示为零。 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 的解决方案,它也有效。我有一个 <canvas> 元素的引用,我试图获得正确的高度,setTimeout(()=>{}, 0)document.onreadystatechange(()=>{}) 都有助于获得正确的高度。
D
Daniele Cruciani

我的 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)发挥您的想象力,您可以在 npm 上找到 reactstrap:将 innerRef 替换为 ref 以获得旧版 dom 元素(例如 <div>)。

useEffect 或 useLayoutEffect

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

useLayoutEffect(或 useEffect)第二个参数

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

我用了

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

因为 myself.current 在渲染之前为空,这是一件好事,不要检查,最后的第二个参数 myself.current.clientHeight 是我要检查的更改。

我在这里解决什么(或试图解决)

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


很好的答案,但会从更简单的例子中受益。基本上是 whiteknight 的答案,但使用 useLayoutEffect 和实际的 div 将 ref 绑定到。
这个答案几乎对我有用,但是当我调整窗口大小时,我看不到高度重新计算。
@carmenism 奇怪。我认为是相反的,应该在每次重新渲染时计算,但也许你可以通过参数更改来强制渲染。关于我不记得传递给 Widget 组件的宽度的代码,但也许这是强制重新渲染组件的一种方式,所以触发 useLayoutEffect() 并重新计算
i
iamakshatjain

与钩子一起使用:

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

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

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

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


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

如果您需要窗口调整大小事件,这是另一个:

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


很好的答案,这就是我要找的东西
M
Mohammad Fallah

使用 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;


typeof window !== 'undefined' 不是必需的,因为 useEffect 已经在客户端上运行。
C
Chunky Chunk

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

在使用 react-window (react-virtualized) 时,我使用此函数来获取列表项渲染器的高度,而不必硬编码所需的 itemSize 道具FixedSizeList

实用程序.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
};

你的解决方案太棒了!它真的救了我! :)
j
jfunk

我发现 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;


D
Dipanjan Panja

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

const [width, setWidth] = useState(0);

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

q
qmn1711

我发现有用的 npm 包 https://www.npmjs.com/package/element-resize-detector

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

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


A
Ali Klein

这是从 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>
  );
};