ReactJS - Dapatkan Ketinggian elemen

121

Bagaimana saya bisa mendapatkan Tinggi sebuah elemen setelah React merender elemen itu?

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

HASIL

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

Ini menghitung tinggi kontainer sebelum render (36px). Saya ingin mendapatkan ketinggian setelah render. Hasil yang benar harus 18px dalam kasus ini. jsfiddle.dll

faia20
sumber
1
Ini bukan pertanyaan bereaksi melainkan pertanyaan Javascript dan DOM. Anda harus mencoba mencari tahu peristiwa DOM mana yang harus Anda gunakan untuk menemukan tinggi akhir elemen Anda. Di event handler, Anda dapat menggunakan setStateuntuk menetapkan nilai ketinggian ke variabel status.
mostruash

Jawaban:

28

Lihat biola ini (benar-benar memperbarui milik Anda)

Anda perlu menghubungkan componentDidMountyang dijalankan setelah metode render. Di sana, Anda mendapatkan ketinggian elemen yang sebenarnya.

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>
Andreyco
sumber
15
tidak benar. lihat jawaban Paul Vincent Beigang di bawah
ekatz
1
IMHO, sebuah komponen seharusnya tidak mengetahui nama wadahnya
Andrés
156

Berikut ini adalah contoh ES6 terkini menggunakan ref .

Ingatlah bahwa kita harus menggunakan komponen kelas React karena kita perlu mengakses metode Lifecycle componentDidMount()karena kita hanya dapat menentukan tinggi elemen setelah dirender di 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'))

Anda dapat menemukan contoh yang sedang berjalan di sini: https://codepen.io/bassgang/pen/povzjKw

Paul Vincent Beigang
sumber
6
Ini berfungsi, tetapi saya mendapatkan beberapa kesalahan eslint untuk panduan gaya Airbnb: <div ref={ divElement => this.divElement = divElement}>{children}</div>melempar "[eslint] Fungsi panah seharusnya tidak mengembalikan tugas. (No-return-assign)" dan this.setState({ height });melempar "[eslint] Jangan gunakan setState di componentDidMount (react / no- did-mount-set-state) ". Adakah yang punya solusi sesuai styleguide?
Timo
7
Bungkus tugas dalam tanda kurung sehingga berperilaku sebagai fungsi (bukan ekspresi), seperti iniref={ divElement => {this.divElement = divElement}}
teletypist
3
Ini akan menjadi jawaban yang diterima :) Selain itu, jika Anda ingin mendapatkan ukuran elemen setelah elemen diperbarui, Anda dapat menggunakan metode ini componentDidUpdatejuga.
jjimenez
4
apakah ada alternatif untuk memanggil this.setState () di componentDidMount dengan contoh ini?
danjones_mcr
3
Solusi bagus. Tetapi tidak akan berfungsi untuk desain responsif. Jika tinggi header 50px untuk desktop, dan pengguna mengecilkan ukuran jendela, dan sekarang tinggi menjadi 100px, solusi ini tidak akan mengambil tinggi yang diperbarui. Mereka perlu menyegarkan halaman untuk mendapatkan efek yang diubah.
TheCoder
97

Bagi mereka yang tertarik menggunakan react hooks, ini mungkin bisa membantu Anda memulai.

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>
  )
}
Mr 14
sumber
11
Terima kasih atas jawaban Anda - ini membantu saya memulai. 2 pertanyaan meskipun: (1) Untuk tugas-tugas tata letak-mengukur ini, harus kita gunakan useLayoutEffectbukan useEffect? Dokumen tampaknya menunjukkan bahwa kita harus melakukannya. Lihat "tip" Bagian sini: reactjs.org/docs/hooks-effect.html#detailed-explanation (2) Untuk kasus ini di mana kita hanya perlu referensi ke elemen anak, yang harus kita gunakan useRefbukan createRef? Dokumen tampaknya menunjukkan bahwa useReflebih sesuai untuk kasus penggunaan ini. Lihat: reactjs.org/docs/hooks-reference.html#useref
Jason Frank
Saya baru saja menerapkan solusi yang menggunakan dua item yang saya sarankan. Mereka tampaknya bekerja dengan baik, tetapi Anda mungkin mengetahui beberapa kelemahan dari pilihan ini? Terima kasih atas bantuan Anda!
Jason Frank
4
@Jason Pertanyaan bagus. 1) UseEffect dan useLayoutEffect akan diaktifkan setelah layout dan paint. Namun, useLayoutEffect akan diaktifkan secara serempak sebelum pengecatan berikutnya. Saya akan mengatakan jika Anda benar-benar membutuhkan konsistensi visual maka gunakan useLayoutEffect, jika tidak, useEffect sudah cukup. 2) Anda benar tentang itu! Dalam komponen fungsional createRef akan membuat ref baru setiap kali komponen dirender. Menggunakan useRef adalah opsi yang lebih baik. Saya akan memperbarui jawaban saya. Terima kasih!
Tn. 14
Ini membantu saya memahami cara menggunakan wasit dengan kait. Saya akhirnya pergi useLayoutEffectsetelah membaca komentar lain di sini. Sepertinya bekerja dengan baik. :)
GuiDoody
1
Saya sedang mengatur dimensi komponen saya useEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight})), dan IDE saya (WebStorm) menyarankan saya ini: ESLint: React Hook useEffect berisi panggilan ke 'setDimensions'. Tanpa daftar dependensi, ini dapat menyebabkan rantai pembaruan yang tak terbatas. Untuk memperbaikinya, berikan [] sebagai argumen kedua ke Hook useEffect. (react-hooks / exhaustive-deps) , jadi berikan []argumen kedua keuseEffect
Akshdeep Singh
9

Anda juga ingin menggunakan referensi pada elemen daripada menggunakan document.getElementById, itu hanya hal yang sedikit lebih kuat.

yoyodunno
sumber
@doublejosh pada dasarnya Anda benar tetapi apa yang harus dilakukan jika Anda perlu melakukan mixup misalnya angularJsdan reactsaat bermigrasi dari satu ke yang lain? Ada beberapa kasus di mana manipulasi DOM langsung sangat dibutuhkan
messerbill
2

Jawaban saya tahun 2020 (atau 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>
    )
}

biarkan menggunakan imajinasi Anda untuk apa yang hilang di sini (WidgetHead), reactstrapadalah sesuatu yang dapat Anda temukan di npm: ganti innerRefdengan refuntuk elemen dom warisan (katakanlah a <div>).

useEffect atau useLayoutEffect

Yang terakhir dikatakan sinkron untuk perubahan

useLayoutEffect(atau useEffect) argumen kedua

Argumen kedua adalah array, dan diperiksa sebelum menjalankan fungsi dalam argumen pertama.

Saya dulu

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

karena diriku.current adalah null sebelum rendering, dan itu adalah hal yang baik untuk tidak diperiksa, parameter kedua di akhir myself.current.clientHeightadalah apa yang ingin saya periksa perubahannya.

apa yang saya selesaikan di sini (atau coba pecahkan)

Di sini saya memecahkan masalah widget pada kisi yang mengubah ketinggiannya sesuai keinginan mereka sendiri, dan sistem kisi harus cukup elastis untuk bereaksi ( https://github.com/STRML/react-grid-layout ).

Daniele Cruciani
sumber
1
jawaban yang bagus, tetapi akan mendapat manfaat dari contoh yang lebih sederhana. Pada dasarnya jawaban whiteknight tetapi dengan useLayoutEffectdan div aktual untuk mengikat wasit.
bot19
1

Menggunakan dengan kait:

Jawaban ini akan membantu jika dimensi konten Anda berubah setelah pemuatan.

onreadystatechange : Terjadi saat status muat data yang termasuk dalam elemen atau dokumen HTML berubah. Peristiwa onreadystatechange diaktifkan pada dokumen HTML saat status muat konten halaman telah berubah.

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

Saya mencoba bekerja dengan pemutar video youtube yang disematkan yang dimensinya dapat berubah setelah memuat.

iamakshatjain
sumber
0

Berikut ini satu lagi jika Anda membutuhkan acara pengubahan ukuran jendela:

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

kode pena

Gfast2
sumber
0

Solusi alternatif, jika Anda ingin mengambil ukuran elemen React secara sinkron tanpa harus merender elemen secara jelas, Anda dapat menggunakan ReactDOMServer dan DOMParser .

Saya menggunakan fungsi ini untuk mendapatkan ketinggian penyaji item daftar saya saat menggunakan react-window ( react-virtualized ) alih-alih harus melakukan hardcode itemSizeprop yang diperlukan untuk 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
};
TheDarkIn1978
sumber