ReactJS - 获取元素的高度

ReactJS - Get Height of an element

本文关键字:高度 元素 获取 ReactJS      更新时间:2023-09-26
如何在

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>

反应

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。斯菲德尔

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

下面是一个使用 ref 的最新 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

看到这个小提琴(实际上是更新你的(

您需要挂钩到在渲染方法之后运行的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>
与其使用 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>
  )
}

它可能显示零。 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>
      )
    }

您还想在元素上使用 refs 而不是使用 document.getElementById ,这只是一个稍微健壮一点的东西。

我的 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替换为遗留 DOM 元素的 ref(比如<div>(。

useEffect 或 useLayoutEffect

最后一个据说是同步的更改

useLayoutEffect(或useEffect(第二个参数

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

我用了

[我自己.当前,我自己.当前

?我自己.当前.客户端高度:0]

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

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

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

与钩子一起使用:

如果您的内容维度在加载后发生变化,此答案将很有帮助。

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

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

我正在尝试使用YouTube视频播放器嵌入,其尺寸在加载后可能会更改。

如果您需要

窗口大小调整事件,这是另一个:

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

码笔

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

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;

>你也可以使用getBoundingClientRect()来获取高度,宽度。

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

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

当使用反应窗口(反应虚拟化(时,我使用此函数来获取我的列表项渲染器的高度,而不必为FixedSizeList硬编码所需的itemSize道具。

公用事业.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
};

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

搜索后,我发现了这篇博客文章,它提供了一个工作 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;
你可以

使用这个钩子

import useMeasure from "react-use-measure";
const [ref, {height}] = useMeasure()

代码的其余部分

<div ref={ref} id="container">
</div>

然后,您可以在所需的位置访问高度,并且每次大小更改时都会更新高度

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

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

可以与 React 组件或功能组件一起使用(对 react 钩子特别有用(

这里有一个很好的可重用钩子,从 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>
  );
};