Как лучше всего иметь резервный образ в NextJS?

Недавно я работал над проектом в NextJS, который использует YoutubeAPI для получения информации о видео, включая URL-адреса миниатюр.

URL-адрес миниатюры для изображения с полным разрешением выглядит следующим образом:

Однако иногда YouTube не удается создать изображение с полным разрешением, и в этом случае изображение не отображается на моей веб-странице.

В случае, если изображение с URL не существует Я хочу использовать другой URL, например https://i.ytimg.com/vi/${videoId}/hqdefault.jpg

Как лучше всего справиться с этим с ?

7 ответов

Решение

Вы можете создать настраиваемый компонент изображения, который расширяет встроенный и добавляет логику отката, если изображение не загружается, запустив 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
    layout="fill"
    src={`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`}
    fallbackSrc={`https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`}
/>

Эти ответы были полезны, но есть способ добиться этого без необходимости проходить keyкаждый раз, пользуясь useEffectкрюк:

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

Есть решение @juliomalves с машинописным текстом

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

interface ImageWithFallbackProps extends ImageProps {
  fallbackSrc: string
}

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

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

export default ImageWithFallback

@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, которое помогает изменить изображение, даже если у вас было изображение, на котором раньше было резервное изображение.

Ни одно из вышеперечисленных решений не сработало для меня. Но это сработало, когда я использовал «onErrorCapture» вместо «onError»! "="

      <Image
    loader={({ src }) => src}
    src={imageUrl ? imageUrl : "/images/no-thumbnail.jpg"}
    width={500}
    height={500}
    alt={imageUrl ? imageUrl : "/images/no-thumbnail.jpg"}
    onError={(event) => {
        event.target.id = "/images/no-thumbnail.jpg";
        event.target.srcset = "/images/no-thumbnail.jpg";
    }}
 />

onError вы можете установить srcset изображения. По изображению srcset nextjs image показывает изображения.

Это моя попытка создать<Avatar>родительский компонент с и<Avatar.Fallback>дочерние компоненты.

Во-первых, я построил<Avatar.Image>компонент для отображения изображения нашего аватара.

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

function AvatarImage({ src, alt, className, ...props }) {
  const [imageSuccessfullyLoaded, setImageSuccessfullyLoaded] = useState(true);

  return (
    <div>
      {imageSuccessfullyLoaded && (
        <Image
          src={src}
          alt={alt}
          placeholder="empty"
          onError={() => {
            setImageSuccessfullyLoaded(false);
          }}
          fill
          {...props}
        />
      )}
    </div>
  );
}

export default AvatarImage;

ОднаждыAvatarImageкомпонент не загружается, я позволяюAvatarFallbackкомпонент заполняет фон.

      import React from 'react';
import classNames from 'classnames';

function AvatarFallback({ children, className }) {
  return (
    <div
      className={classNames(
        'text-lg font-medium text-neutral-600 dark:text-neutral-200',
        className
      )}
    >
      {children}
    </div>
  );
}

export default AvatarFallback;

Родительский компонент имеет имяAvatar

      import React from 'react';
import classNames from 'classnames';
import AvatarImage from './AvatarImage/AvatarImage';
import AvatarFallback from './AvatarFallback/AvatarFallback';


function Avatar({ className, children }) {
  return (
    <div
      className={classNames(
        'relative flex items-center justify-center overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-600',
        className
      )}
    >
      {children}
    </div>
  );
}

Avatar.Image = AvatarImage;
Avatar.Fallback = AvatarFallback;

export default Avatar;

Ниже приведен пример того, как я его использую: (не забудьте изменить${videoId})

      <Avatar>
  <Avatar.Image src="https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg" alt="Avatar image" />
  <Avatar.Fallback>
    <Image
      src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg"
      fill
      alt="Avatar image"
    />
  </Avatar.Fallback>
</Avatar>
Другие вопросы по тегам