在 NextJS 中使用后备图像的最佳方式是什么?

What is the best way to have a fallback image in NextJS?

最近在NextJS做一个项目,使用YoutubeAPI获取视频信息,包括缩略图URLs。

全分辨率图片的缩略图 URL 如下所示: https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg

但是,有时 YouTube 无法生成全分辨率图像,在这种情况下,图像不会显示在我的网页上。

如果带有 URL https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg 的图像不存在我希望使用另一个 URL 比如 https://i.ytimg.com/vi/${videoId}/hqdefault.jpg

使用 next/image 处理此问题的最佳方法是什么?

您可以创建一个自定义图像组件来扩展内置 next/image 并在图像加载失败时通过触发 onError 回调添加回退逻辑。

import React, { useState } from 'react';
import Image from 'next/image';

const ImageWithFallback = (props) => {
    const { src, fallbackSrc, ...rest } = props;
    const [imgSrc, setImgSrc] = useState(src);

    return (
        <Image
            {...rest}
            src={imgSrc}
            onError={() => {
                setImgSrc(fallbackSrc);
            }}
        />
    );
};

export default ImageWithFallback;

那么,可以直接使用自定义组件代替next/image如下:

<ImageWithFallback
    key={videoId}
    layout="fill"
    src={`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`}
    fallbackSrc={`https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`}
/>

传递 key 道具以在 videoId 更改时触发重新渲染。

@juliomalves 给出了 99% 的答案,但我想补充一下。 在他的解决方案中更改 src 时出现问题,因为图像不会更新,因为它正在获取未更新的 imgSrc 值。这是我对他的回答的补充:

import React, { useState } from 'react';
import Image from 'next/image';

const ImageFallback = (props) => {
    
    const { src, fallbackSrc, ...rest } = props;
    const [imgSrc, setImgSrc] = useState(false);
    const [oldSrc, setOldSrc] = useState(src);
    if (oldSrc!==src)
    {
        setImgSrc(false)
        setOldSrc(src)
    }
    return (
        <Image
            {...rest}
            src={imgSrc?fallbackSrc:src}
            onError={() => {
                setImgSrc(true);
            }}
        />
    );
};

export default ImageFallback;

现在 imgSrc 仅用作标志,并且跟踪 src 值,这有助于更改图像,即使您的图像之前有备用图像。

这些答案很有帮助,但有一种方法可以利用 useEffect 钩子来实现这一点,而无需每次都传递 key

useEffect(() => {
    set_imgSrc(src);
}, [src]);

此外,onError 事件似乎不会针对某些图像触发(我相信 layout='fill' 在某些情况下不会触发它),对于那些我一直在使用的情况onLoadingComplete 然后我检查图像的宽度是否为 0

onLoadingComplete={(result) => {
    if (result.naturalWidth === 0) {  // Broken image
        set_imgSrc(fallbackSrc);
    }
}}

完整代码:

import Image from "next/image";
import { useEffect, useState } from "react";

export default function ImageFallback({ src, fallbackSrc, ...rest }) {
  const [imgSrc, set_imgSrc] = useState(src);

  useEffect(() => {
    set_imgSrc(src);
  }, [src]);

  return (
    <Image
      {...rest}
      src={imgSrc}
      onLoadingComplete={(result) => {
        if (result.naturalWidth === 0) {
          // Broken image
          set_imgSrc(fallbackSrc);
        }
      }}
      onError={() => {
        set_imgSrc(fallbackSrc);
      }}
    />
  );
}