懒加载图片

渲染一个支持懒加载的图片。

  • 使用 useState() 钩子创建一个有状态的值,表示图片是否已加载。
  • 使用 useEffect() 钩子检查 HTMLImageElement.prototype 是否包含 'loading',从而检查原生是否支持懒加载。如果不支持,创建一个新的 IntersectionObserver 并使用 IntersectionObserver.observer() 观察 <img> 元素。使用钩子的返回值在组件卸载时进行清理。
  • 使用 useCallback() 钩子来记忆一个回调函数,用于 IntersectionObserver。这个回调函数将更新 isLoaded 状态变量,并使用 IntersectionObserver.disconnect() 断开 IntersectionObserver 实例。
  • 使用 useRef() 钩子创建两个引用。一个引用将保存 <img> 元素,另一个引用将保存 IntersectionObserver 实例(如果需要)。
  • 最后,使用给定的属性渲染 <img> 元素。如果需要,应用 loading='lazy' 进行懒加载。使用 isLoaded 来确定 src 属性的值。
const LazyLoadImage = ({
  alt,
  src,
  className,
  loadInitially = false,
  observerOptions = { root: null, rootMargin: '200px 0px' },
  ...props
}) => {
  const observerRef = React.useRef(null);
  const imgRef = React.useRef(null);
  const [isLoaded, setIsLoaded] = React.useState(loadInitially);

  const observerCallback = React.useCallback(
    entries => {
      if (entries[0].isIntersecting) {
        observerRef.current.disconnect();
        setIsLoaded(true);
      }
    },
    [observerRef]
  );

  React.useEffect(() => {
    if (loadInitially) return;

    if ('loading' in HTMLImageElement.prototype) {
      setIsLoaded(true);
      return;
    }

    observerRef.current = new IntersectionObserver(
      observerCallback,
      observerOptions
    );
    observerRef.current.observe(imgRef.current);
    return () => {
      observerRef.current.disconnect();
    };
  }, []);

  return (
    <img
      alt={alt}
      src={isLoaded ? src : ''}
      ref={imgRef}
      className={className}
      loading={loadInitially ? undefined : 'lazy'}
      {...props}
    />
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <LazyLoadImage
    src="https://picsum.photos/id/1080/600/600"
    alt="草莓"
  />
);