【问题标题】:React: How do you lazyload image from API response?React:你如何从 API 响应中延迟加载图像?
【发布时间】:2020-12-03 21:12:34
【问题描述】:

我的网站太重了,因为它在从服务器(Google 的 Firebase Firestore)获取数据后下载了 200-400 张图片。

我想出了两个解决方案,希望有人回答其中一个:

  • 我想将每个 img 设置为加载状态,并让访问者在加载之前看到占位符图像。由于在从服务器获取数据之前我不知道我得到了多少图像,我发现很难通过 useState 初始化图像加载状态。这可能吗?那怎么办?
  • 如何延迟加载图像?图像使用占位符初始化。当滚动条靠近图像时,图像开始下载以替换占位符。
function sample() {}{
  const [items, setItems] = useState([])
  const [imgLoading, setImgLoading] = useState(true)  // imgLoading might have to be boolean[]
  useEffect(() => {
    axios.get(url).
    .then(response => setItems(response.data))
  }, [])
  return (
    items.map(item => <img src={item.imageUrl} onLoad={setImgLoading(false)} />)
  )
}

【问题讨论】:

  • 您可能还希望将 一些 加载状态与每个图像关联起来,而不是仅将单个整体加载状态关联起来,或者这就是您真正要问的?
  • 我想制作imgLoading[],它的长度是服务器响应中的数组长度,但在得到服务器响应之前我不知道长度。
  • 第二个问题的答案是我认为这个库npmjs.com/package/react-lazy-load
  • 你获取了所有的图片 url,并将它们映射到图片中显示?
  • 类似this?

标签: javascript reactjs react-hooks


【解决方案1】:

有这方面的库,但如果你想自己动手,你可以使用IntersectionObserver,类似这样:

const { useState, useRef, useEffect } = React;

const LazyImage = (imageProps) => {
  const [shouldLoad, setShouldLoad] = useState(false);
  const placeholderRef = useRef(null);

  useEffect(() => {
    if (!shouldLoad && placeholderRef.current) {
      const observer = new IntersectionObserver(([{ intersectionRatio }]) => {
        if (intersectionRatio > 0) {
          setShouldLoad(true);
        }
      });
      observer.observe(placeholderRef.current);
      return () => observer.disconnect();
    }
  }, [shouldLoad, placeholderRef]);

  return (shouldLoad 
    ? <img {...imageProps}/> 
    : <div className="img-placeholder" ref={placeholderRef}/>
  );
};

ReactDOM.render(
  <div className="scroll-list">
    <LazyImage src='https://i.insider.com/536a52d9ecad042e1fb1a778?width=1100&format=jpeg&auto=webp'/>
    <LazyImage src='https://www.denofgeek.com/wp-content/uploads/2019/12/power-rangers-beast-morphers-season-2-scaled.jpg?fit=2560%2C1440'/>
    <LazyImage src='https://i1.wp.com/www.theilluminerdi.com/wp-content/uploads/2020/02/mighty-morphin-power-rangers-reunion.jpg?resize=1200%2C640&ssl=1'/>
    <LazyImage src='https://m.media-amazon.com/images/M/MV5BNTFiODY1NDItODc1Zi00MjE2LTk0MzQtNjExY2I1NTU3MzdiXkEyXkFqcGdeQXVyNzU1NzE3NTg@._V1_CR0,45,480,270_AL_UX477_CR0,0,477,268_AL_.jpg'/>
  </div>,
  document.getElementById('app')
);
.scroll-list > * {
  margin-top: 400px;
}

.img-placeholder {
  content: 'Placeholder!';
  width: 400px;
  height: 300px;
  border: 1px solid black;
  background-color: silver;
}
<div id="app"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

此代码会在占位符在屏幕上可见时立即加载它们,但如果您想要更大的检测余量,您可以调整 IntersectionObserverrootMargin 选项,使其在仍然略微关闭时开始加载屏幕。

【讨论】:

  • observer.observe(placeholderRef.current);引发输入错误,*“null”类型的参数不可分配给“Element”类型的参数。ts(2345)*。你知道怎么解决吗?
  • 如果你使用 TypeScript,你会想要const placeholderRef = useRef&lt;Element | null&gt;(null);
【解决方案2】:

我会创建一个Image 组件来处理它自己的相关状态。然后在这个组件中,我会使用IntersectionObserver API 来判断图像的容器是否在用户的浏览器上可见。

我将拥有isLoadingisInview 状态,isLoading 将始终为true,直到isInview 更新为true

虽然isLoadingtrue,但我会将null 用作图像的src,并将显示占位符。

当容器在用户浏览器上可见时,仅加载 src

function Image({ src }) {
  const [isLoading, setIsLoading] = useState(true);
  const [isInView, setIsInView] = useState(false);
  const root = useRef(); // the container

  useEffect(() => {
    // sets `isInView` to true until root is visible on users browser

    const observer = new IntersectionObserver(onIntersection, { threshold: 0 });
    observer.observe(root.current);

    function onIntersection(entries) {
      const { isIntersecting } = entries[0];

      if (isIntersecting) { // is in view
        observer.disconnect();
      }

      setIsInView(isIntersecting);
    }
  }, []);

  function onLoad() {
    setIsLoading((prev) => !prev);
  }

  return (
    <div
      ref={root}
      className={`imgWrapper` + (isLoading ? " imgWrapper--isLoading" : "")}
    >
      <div className="imgLoader" />
      <img className="img" src={isInView ? src : null} alt="" onLoad={onLoad} />
    </div>
  );
}

我还会有 CSS 样式来切换占位符和图像的 display 属性。

.App {
  --image-height: 150px;
  --image-width: var(--image-height);
}

.imgWrapper {
  margin-bottom: 10px;
}

.img {
  height: var(--image-height);
  width: var(--image-width);
}

.imgLoader {
  height: 150px;
  width: 150px;
  background-color: red;
}

/* container is loading, hide the img */
.imgWrapper--isLoading .img {
  display: none;
}

/* container not loading, display img */
.imgWrapper:not(.imgWrapper--isLoading) .img {
  display: block;
}

/* container not loading, hide placeholder */
.imgWrapper:not(.imgWrapper--isLoading) .imgLoader {
  display: none;
}

现在我的父组件将执行对所有图像 url 的请求。它也有自己的isLoading 状态,当设置true 时会显示它自己的占位符。当图像 url 的请求解决后,我将映射到每个 url 以呈现我的 Image 组件。

export default function App() {
  const [imageUrls, setImageUrls] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    fetchImages().then((response) => {
      setImageUrls(response);
      setIsLoading((prev) => !prev);
    });
  }, []);

  const images = imageUrls.map((url, index) => <Image key={index} src={url} />);

  return <div className="App">{isLoading ? "Please wait..." : images}</div>;
}

【讨论】:

    【解决方案3】:

    将响应数据映射到“isLoading”布尔值数组,并更新回调以获取索引并更新特定的“isLoading”布尔值。

    function Sample() {
      const [items, setItems] = useState([]);
      const [imgLoading, setImgLoading] = useState([]);
    
      useEffect(() => {
        axios.get(url).then((response) => {
          const { data } = response;
          setItems(data);
          setImgLoading(data.map(() => true));
        });
      }, []);
    
      return items.map((item, index) => (
        <img
          src={item.imageUrl}
          onLoad={() =>
            setImgLoading((loading) =>
              loading.map((el, i) => (i === index ? false : el))
            )
          }
        />
      ));
    }
    

    【讨论】:

      猜你喜欢
      • 2016-12-08
      • 1970-01-01
      • 1970-01-01
      • 2020-08-12
      • 2023-03-07
      • 1970-01-01
      • 2014-06-18
      • 2014-10-05
      • 2017-05-19
      相关资源
      最近更新 更多