【问题标题】:Reactjs - Get Height of a div/image using React HooksReactjs - 使用 React Hooks 获取 div/图像的高度
【发布时间】:2020-05-18 07:29:32
【问题描述】:

我想使用反应钩子在反应功能组件中获取图像的高度

我使用了以下代码:

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

这样做的问题是它返回 clientHeight 为 0,但 useEffect 中的 console.log 具有正确的 clientHeight,如下图所示。

这意味着 ref &amp;&amp; ref.current &amp;&amp; ref.current.clientHeight 在调用时未定义,但在同一 useEffect 中的控制台显示 refcurrent: ref.current 的值正确,但 clientHeight: ref.current.clientHeight 为零。

同样,我不能在useEffect 中使用....}, [ref &amp;&amp; ref.current &amp;&amp; ref.current.clientHeight],因为useEffect 不接受复杂的表达式。如果我将useEffect 外部或内部的变量定义为const clientHeight = (ref &amp;&amp; ref.current &amp;&amp; ref.current.clientHeight) || 0,那就不走运了!

任何人都可以在这方面提供帮助。谢谢!

【问题讨论】:

  • 你可以尝试将 ref 传递为使用效果的依赖项,而不是仅在 mount 时执行副作用吗?
  • 附言。如果你想挂钩到事件循环的 Dom 渲染端,你也有 useLayoutEffect
  • @quirimmo - 来自the description of useLayoutEffect,我认为与useEffect 相比,在获取此信息方面不会更好,甚至可能更糟。它发生得更快,而且是同步的。
  • 这就是为什么如你所见我告诉你的第一件事就是将 ref 放在依赖项中:)

标签: javascript reactjs react-redux


【解决方案1】:

正如其他人在这里提到的,在大多数情况下,您的逻辑发生在图像加载之前。因此,您必须在图像加载后获取图像尺寸,因为在此之前浏览器不知道图像尺寸。

我知道你提到你想用钩子来做,但除非你做一些可重用的事情,否则这里可能不需要钩子。或者除非你想更好地理解它们,否则没关系。

一种选择是只在图像元素上使用onLoad 事件。这样,如果您在 ref.currentimg.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}
    />
  )
}

不要误会我的意思,我绝对喜欢钩子,但它们并不总是解决方案。

【讨论】:

  • 这比在事后挂钩load 事件好多。请注意,图像可以加载但尚未布置
  • combined mine with yours,这是一个显着的改进。
  • 谢谢。谁会想到图片加载实际上需要这么多思考。
  • :-) 是的。起初我没有意识到有问题的元素是图像......很高兴你过来并指出挂钩load的简单方法!
  • 没错,提供的代码示例有一个&lt;img&gt;,但我只是注意到问题标题提到了“div/image”,但下面的问题只是指图像而不是 div。因此,您的两种解决方案实际上都涵盖了这两种情况。最后,OP 现在根据情况几乎没有可供选择的选项。
【解决方案2】:

最有可能的是,当您的效果执行时,图像尚未加载,因此其高度为 0。稍后当它被加载时,它会更新节点的属性,这就是您在控制台中看到的内容(因为您正在记录节点实例)。

您可以像这样在useEffect 回调中订阅加载事件:

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

【讨论】:

  • 挂钩load 之后元素在带有src的DOM中是不可靠的。
  • 是的,我同意你的看法。可以添加另一个检查以确定图像是否已经加载。在后一种情况下,需要添加负载侦听器,在前一种情况下,可以立即使用图像尺寸。
  • 是的,这是有道理的。谢谢你,亚历克斯!
  • @Owais - 请注意,如答案所示,这是不可靠的。您需要处理上面提到的竞争条件。当图像来自缓存时,该问题将特别明显。您可以查看ref.current.complete
  • 即使使用complete,但如果您的时间只是错误,那么当您检查时它还不会布置图像...
【解决方案3】:

注意:我最初忽略了被测量元素是img 的事实。嗬!所以我发布了一个community wiki answer combining the below with the onload 显示在Marko's answer 中。


useEffect 回调在渲染后立即发生;浏览器还没有机会布局元素。由于this feature of console,您稍后会看到正确的高度。

在大多数现代浏览器上,您应该能够使用值为0setTimeoout 来解决此问题。或者,或者,两个requestAnimationFrame 回调(第一个将发生在元素布局之前之前,因此没有帮助;第二个是之后)。

例如:

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;
  };
}, []);

当然,高度可以根据其他事物的变化而变化......

如果要在元素变化时更新高度,请将其添加为依赖项:

}, [ref.current]);

不过,这只会在元素更改时更新它;如果有其他东西改变了元素的高度,那将不会再次触发效果回调。

现场示例:

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>

【讨论】:

  • 抱歉,我不小心对您的答案投了反对票(而是投了赞成票,因为它似乎没有办法撤消:))。你要做的基本上是引入一个轮询,它将运行多次,直到图像被加载并且它的尺寸是已知的。同样可以通过添加负载监听器来实现。
  • @AlexV - 天真的load 侦听器方法有一个竞争条件。是的,您可以结合使用completeload,但上面的轮询非常轻量级,不会经常发生,无需担心。唯一真正的问题是高度为零的图像。 :-)
  • 无论如何,OP 有几个不错的选择可供选择,所以我们已经完成了我们的工作。 :-)
  • 轮询发生的频率取决于网络条件和图像的大小(连接越慢,图像越大,下载的时间越长,轮询回调执行的次数也越多)。
  • @AlexV - 这是真的,但同样,回调是微不足道的。这不存在load 解决方案所具有的图像被加载 但尚未布置 的问题,这对我来说是一个更大的问题。最强大但最有效的解决方案可能是两种方法的组合,但如果只选择一种方法,我更喜欢稳健且高效的方法。
【解决方案4】:

不管怎样,这里是Marko's solutionmine 的组合。它不会尝试查找客户端高度,直到/除非图像已加载,但最多会使用两次requestAnimationFrame(因为它不应该超过两个,并且通常不会使用任何或只使用一个)到如果图像尚未布局,则获取高度:

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>

【讨论】:

    【解决方案5】:

    这样的事情怎么样。

    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"
            />
        )
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-15
      • 1970-01-01
      • 2011-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多