ReactJS - Nhận Chiều cao của một phần tử


121

Làm cách nào để lấy Chiều cao của một phần tử sau khi React hiển thị phần tử đó?

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

KẾT QUẢ

Size: 36px but it should be 18px after the render

Nó tính toán chiều cao vùng chứa trước khi kết xuất (36px). Tôi muốn lấy chiều cao sau khi kết xuất. Kết quả đúng phải là 18px trong trường hợp này. jsfiddle


1
Đây không phải là câu hỏi phản ứng mà là câu hỏi về Javascript và DOM. Bạn nên cố gắng tìm ra sự kiện DOM nào bạn nên sử dụng để tìm chiều cao cuối cùng của phần tử của bạn. Trong trình xử lý sự kiện, bạn có thể sử dụng setStateđể gán giá trị chiều cao cho một biến trạng thái.
mostruash

Câu trả lời:


28

Xem này fiddle (trên thực tế cập nhật của bạn)

Bạn cần phải hook vào componentDidMountđó được chạy sau phương thức kết xuất. Ở đó, bạn nhận được chiều cao thực tế của phần tử.

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
không chính xác. xem câu trả lời Paul Vincent Beigang của bên dưới
ekatz

1
IMHO, một thành phần không nên biết tên của thùng chứa của nó
Andrés

156

Sau đây là một cập nhật ES6 ví dụ sử dụng một ref .

Hãy nhớ rằng chúng ta phải sử dụng một thành phần lớp React vì chúng ta cần truy cập vào phương thức Lifecycle componentDidMount()vì chúng ta chỉ có thể xác định chiều cao của một phần tử sau khi nó được hiển thị trong 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'))

Bạn có thể tìm thấy ví dụ đang chạy tại đây: https://codepen.io/bassgang/pen/povzjKw


6
Điều này hoạt động nhưng tôi gặp phải một số lỗi eslint đối với Airbnb styleguide: <div ref={ divElement => this.divElement = divElement}>{children}</div>throws "[eslint] Hàm mũi tên không trả lại phép gán. (No-return- this.setState({ height });allow )" và ném "[eslint] Không sử dụng setState trong componentDidMount (react / no- did-mount-set-state) ". Bất cứ ai có một giải pháp tuân thủ hướng dẫn phong cách?
Timo

7
Quấn sự phân công trong niềng răng để nó cư xử như một chức năng (chứ không phải là một biểu hiện), như thế nàyref={ divElement => {this.divElement = divElement}}
teletypist

3
Đây có thể là câu trả lời được chấp nhận :) Ngoài ra, nếu bạn muốn nhận kích thước của phần tử sau khi phần tử được cập nhật, bạn có thể sử dụng phương pháp này componentDidUpdate.
jjimenez

4
có cách nào thay thế để gọi this.setState () trong componentDidMount với ví dụ này không?
danjones_mcr

3
Giải pháp tốt. Nhưng sẽ không hoạt động đối với các thiết kế đáp ứng. Nếu chiều cao tiêu đề là 50px đối với máy tính để bàn và người dùng thu nhỏ kích thước cửa sổ và bây giờ chiều cao trở thành 100px, giải pháp này sẽ không tính đến chiều cao được cập nhật. Họ cần làm mới trang để có được các hiệu ứng đã thay đổi.
TheCoder

96

Đối với những người quan tâm đến việc sử dụng react hooks, điều này có thể giúp bạn bắt đầu.

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
Cảm ơn câu trả lời của bạn - nó đã giúp tôi bắt đầu. Tuy nhiên, có 2 câu hỏi: (1) Đối với các nhiệm vụ đo lường bố cục này, chúng ta có nên sử dụng useLayoutEffectthay thế useEffectkhông? Các tài liệu dường như chỉ ra rằng chúng ta nên làm. Xem phần "mẹo" tại đây: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Đối với những trường hợp chúng ta chỉ cần tham chiếu đến một phần tử con, chúng ta có nên sử dụng useRefthay thế createRefkhông? Các tài liệu dường như chỉ ra rằng useRefthích hợp hơn cho trường hợp sử dụng này. Xem: reactjs.org/docs/hooks-reference.html#useref
Jason Frank

Tôi vừa thực hiện một giải pháp sử dụng hai mục mà tôi đang đề xuất. Chúng có vẻ hoạt động tốt, nhưng bạn có thể biết một số mặt trái của những lựa chọn này? Cảm ơn bạn đã giúp đỡ!
Jason Frank

4
@JasonFrank Câu hỏi hay. 1) Cả useEffect và useLayoutEffect sẽ được kích hoạt sau khi bố trí và sơn. Tuy nhiên, useLayoutEffect sẽ bắn đồng bộ trước khi sơn tiếp theo. Tôi muốn nói nếu bạn thực sự cần sự nhất quán trực quan thì hãy sử dụng useLayoutEffect, nếu không, useEffect là đủ. 2) Bạn nói đúng về điều đó! Trong một thành phần chức năng, createRef sẽ tạo một tham chiếu mới mỗi khi thành phần đó được hiển thị. Sử dụng useRef là lựa chọn tốt hơn. Tôi sẽ cập nhật câu trả lời của tôi. Cảm ơn!
Ông 14

Điều này đã giúp tôi hiểu cách sử dụng ref với hooks. Tôi đã kết thúc với useLayoutEffectsau khi đọc các bình luận khác ở đây. Có vẻ hoạt động tốt. :)
GuiDoody

1
Tôi đang thiết lập các kích thước cho thành phần của mình useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))và IDE (WebStorm) đã gợi ý cho tôi điều này: ESLint: React Hook useEffect chứa một lệnh gọi đến 'setDimensions'. Nếu không có danh sách các phụ thuộc, điều này có thể dẫn đến một chuỗi cập nhật vô hạn. Để khắc phục điều này, hãy chuyển [] làm đối số thứ hai cho useEffect Hook. (phản ứng-móc / đầy đủ-DEPS) , do đó vượt qua []như là đối số thứ hai để bạnuseEffect
Akshdeep Singh

9

Bạn cũng sẽ muốn sử dụng refs trên phần tử thay vì sử dụng document.getElementById, nó chỉ là một thứ mạnh mẽ hơn một chút.


@doublejosh về cơ bản bạn đúng nhưng phải làm gì nếu bạn cần kết hợp ví dụ angularJsreacttrong khi di chuyển từ cái này sang cái khác? Có những trường hợp thực sự cần thao tác DOM trực tiếp
messerbill

2

Câu trả lời vào năm 2020 (hoặc 2019) của tôi

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

hãy sử dụng trí tưởng tượng của bạn cho những gì còn thiếu ở đây (WidgetHead), reactstraplà thứ bạn có thể tìm thấy trên npm: thay thế innerRefbằng refcho một phần tử dom cũ (giả sử a <div>).

useEffect hoặc useLayoutEffect

Cuối cùng được cho là đồng bộ cho các thay đổi

useLayoutEffect(hoặc useEffect) đối số thứ hai

Đối số thứ hai là một mảng và nó được kiểm tra trước khi thực thi hàm trong đối số đầu tiên.

Tôi đã sử dụng

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

vì bản thân tôi.current là null trước khi hiển thị và đó là điều tốt không nên kiểm tra, tham số thứ hai ở cuối myself.current.clientHeightlà những gì tôi muốn kiểm tra các thay đổi.

những gì tôi đang giải quyết ở đây (hoặc đang cố gắng giải quyết)

Ở đây tôi đang giải quyết vấn đề tiện ích con trên lưới tự thay đổi chiều cao của nó và hệ thống lưới phải đủ đàn hồi để phản ứng ( https://github.com/STRML/react-grid-layout ).


1
câu trả lời tuyệt vời, nhưng sẽ được hưởng lợi từ ví dụ đơn giản hơn. Về cơ bản câu trả lời của whiteknight nhưng với useLayoutEffectvà div thực sự để buộc ref.
bot19

1

Sử dụng với móc:

Câu trả lời này sẽ hữu ích nếu thứ nguyên nội dung của bạn thay đổi sau khi tải.

onreadystatechange : Xảy ra khi trạng thái tải của dữ liệu thuộc về một phần tử hoặc tài liệu HTML thay đổi. Sự kiện onreadystatechange được kích hoạt trên tài liệu HTML khi trạng thái tải của nội dung trang đã thay đổi.

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

Tôi đang cố gắng làm việc với nhúng trình phát video youtube có kích thước có thể thay đổi sau khi tải.


0

Tôi thấy gói npm hữu ích https://www.npmjs.com/package/element-resize-detector

Trình nghe thay đổi kích thước trên nhiều trình duyệt được tối ưu hóa cho các phần tử.

Có thể sử dụng nó với thành phần React hoặc thành phần chức năng (Đặc biệt hữu ích cho các móc phản ứng)


0

Đây là một cái khác nếu bạn cần sự kiện thay đổi kích thước cửa sổ:

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

bút mã


0

Một giải pháp thay thế, trong trường hợp bạn muốn truy xuất kích thước của một phần tử React một cách đồng bộ mà không cần phải hiển thị phần tử một cách rõ ràng, bạn có thể sử dụng ReactDOMServerDOMParser .

Tôi sử dụng chức năng này để lấy chiều cao của trình kết xuất mục trong danh sách của tôi khi sử dụng cửa sổ phản ứng ( phản ứng ảo hóa ) thay vì phải mã hóa phần cứng cần thiết itemSizecho một FixedSizeList .

tiện ích.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
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.