简体   繁体   中英

Reactjs - Get Height of a div/image using React Hooks

I want to get Height of an image in a react functional component using react hooks.

I have used below code:

import React, { useRef, useEffect } from 'react'

const DepthChartContent = props => {
  const ref = useRef()

  useEffect(() => {
    if (ref && ref.current && ref.current.clientHeight) {
      console.log('called')
    }
    console.log('useEffect', {
      ref,
      current: ref.current,
      clientHeight: ref.current.clientHeight,
    })
  }, [])

  return (
    <img
      ref={ref}
      className="depth-reference-bar"
      src={DepthRuler}
      alt="no ruler found"
    />
  )
}

The problem with this is that it returns the clientHeight as 0 but console.log in useEffect has the correct clientHeight as shown in below pic.

参考控制台图像

This means that ref && ref.current && ref.current.clientHeight is not defined when called but consoling in same useEffect is showing correct value for ref , current: ref.current but clientHeight: ref.current.clientHeight is ZERO.

Similarly, I can't use ....}, [ref && ref.current && ref.current.clientHeight] in useEffect because useEffect do not accept complex expression. If I defined a variable outside or inside useEffect as const clientHeight = (ref && ref.current && ref.current.clientHeight) || 0 const clientHeight = (ref && ref.current && ref.current.clientHeight) || 0 , no luck!

Can anyone help in this regards. Thanks!

As others mentioned here, your logic happens before image is loaded in most cases. So you have to get the image dimensions after the image has loaded because the browser doesn't know the image dimensions before that.

I know you mentioned you want to do it with hooks, but unless you are doing something reusable, there's probably no need for hooks here. Or unless you are trying to understand them better, then it's fine.

One option would be to just use the onLoad event on the image element. This way you don't have to do all these checks if you have something in ref.current or is img.complete === true .

import React from 'react'

const DepthChartContent = props => {
  const handleImageLoad = (event) => {
    // Do whatever you want here
    const imageHeight = event.target.clientHeight;
  }

  return (
    <img
      ref={ref}
      className="depth-reference-bar"
      src={DepthRuler}
      alt="no ruler found"
      onLoad={handleImageLoad}
    />
  )
}

Don't get me wrong, I absolutely love hooks, but they aren't always the solution.

Note: I originally missed the fact the element being measured is an img . Doh! So I posted a community wiki answer combining the below with the onload shown in Marko's answer .


useEffect callbacks happen immediately after render; the browser won't have had a chance to layout the elements yet. You're seeing the correct height later becaue of this feature of console .

On most modern browsers, you should be able to fix this using a setTimeoout with the value 0 . Or, alternately, two requestAnimationFrame callbacks (the first will happen just before the element is laid out, so not helpful; the second is afterward).

For instance:

useEffect(() => {
  let cancelled = false;
  const getHeight = () => {
    const { current } = ref; // `ref` will never be falsy
    if (!current || !current.clientHeight) {
      if (!cancelled) {
        requestAnimationFrame(getHeight);
      }
    } else {
      // Use `current.clientHeight` here
    }
  };
  getHeight();
  return () => {
      cancelled = true;
  };
}, []);

Of course, the height can change based on other things changing subsequently...

If you want to update the height when the element changes, add it as a dependency:

}, [ref.current]);

That will only update it when the element changes, though; if something else changes to change the height of the element, that won't fire the effect callback again.

Live Example:

 const { useRef, useEffect } = React; const DepthRuler = "https://via.placeholder.com/150"; const DepthChartContent = props => { const ref = useRef() const counterRef = useRef(0); // Just to count loops useEffect(() => { let cancelled = false; const getHeight = () => { ++counterRef.current; const { current } = ref; // `ref` will never be falsy if (.current ||;current.clientHeight) { if (.cancelled) { requestAnimationFrame(getHeight). } } else { // Use `current,clientHeight` here console:log(`Height is ${current.clientHeight}; getHeight calls; ${counterRef;current}`); } }; getHeight(), return () => { cancelled = true; }. }, []). return ( <img ref={ref} className="depth-reference-bar" src={DepthRuler} alt="no ruler found" /> ) } ReactDOM;render(<DepthChartContent />, document.getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Most likely when your effect executes the image hasn't been loaded yet thus its height is 0. Later when it has been loaded it updates the node's attributes and that's what you see in the console (since you're logging the node instance).

You can subscribe to the load event in your useEffect callback like this:

  useEffect(() => {
      ref.current.addEventListener('load', () => {
          console.log(ref.current.clientHeight);
      });
  }, [ref])

For what it's worth, here's a combination of Marko's solution and mine . It doesn't try to find the client height until/unless the image has been loaded, but will use requestAnimationFrame at most twice (because it should never take more than two, and frequently won't take any or just one) to get the height if the image hasn't been laid out yet:

 const { useEffect } = React; const DepthRuler = "https://via.placeholder.com/150"; const DepthChartContent = props => { const handleImageLoad = (event) => { const {target} = event; let counter = 0; const done = () => { const imageHeight = target.clientHeight; console.log(`Height: ${imageHeight} (counter: ${counter})`); }; const maybeDone = () => { if (target.clientHeight) { done(); } else if (++counter < 3) { requestAnimationFrame(maybeDone); } else { console.log("Couldn't get height"); } }; maybeDone(); }; return ( <img className="depth-reference-bar" src={DepthRuler} alt="no ruler found" onLoad={handleImageLoad} /> ) } ReactDOM.render(<DepthChartContent />, document.getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

How about something like this.

import React, { useRef, useEffect } from 'react'

const DepthChartContent = props => {

    const ref = useRef();
    const [imageHeight, setImageHeight] = useState(0);

    useEffect(() => {
        setImageHeight(ref.current.clientHeight)
    }, [ref.current]);

    return (
        <img
            ref={ref}
            className="depth-reference-bar"
            src={DepthRuler}
            alt="no ruler found"
        />
    )
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM