nuxt / image

Plug-and-play image optimization for Nuxt applications.
https://image.nuxt.com
MIT License
1.34k stars 271 forks source link

Setting fallback image on 404 respons after fetch #1022

Open danielohling opened 1 year ago

danielohling commented 1 year ago

Hi

I'm not really sure how this is supposed to work? There is nothing in the documentation that shows how to do it. It says that you can use Native events such as load or error.

I have tried add a fallback image when the response comes back as 404 direct on the NuxtImg components like so:

<NuxtImg :src="item.ImageURL" width="120" height="120" @error="$event.target.src = fallbackImg" />

but without luck 😢

I've also tried making a function that returns the url to another fallback image but no luck.

It feels like the error event is emitted long after all the images are looped? Or Is there a way to catch it before?

xlanex6 commented 10 months ago

Hey @danielohling

Have you found a solution, I'm facing the same issue. Still exploring

Thanks

vasilistotskas commented 10 months ago

Same issue here

vuthanhbayit commented 9 months ago

Hey @danielohling

Have you found a solution, I'm facing the same issue. Still exploring

Thanks

This is my solution:

<script lang="ts">
import { defineComponent } from 'vue'
import { imgProps } from '@nuxt/image/dist/runtime/components/nuxt-img'

export default defineComponent({
  props: imgProps,

  setup(props) {
    const { fallbackImage } = useAppConfig()

    const src = ref(props.src)

    watch(
      () => props.src,
      value => {
        src.value = value
      },
    )

    return () =>
      h(resolveComponent('NuxtImg'), {
        ...props,
        src: src.value,
        onError: () => {
          src.value = fallbackImage
        },
      })
  },
})
</script>
imnaK commented 9 months ago

You could use

<NuxtImg :src="original.png" :placeholder="placeholder.png" />

as the placeholder acts like a fallback too if the image fails to load. (Got it from the Nuxt Discord)

oemer-aran commented 8 months ago

You could use

<NuxtImg :src="original.png" :placeholder="placeholder.png" />

as the placeholder acts like a fallback too if the image fails to load. (Got it from the Nuxt Discord)

This only works for NuxtImg and not NuxtPicture. Also the placeholder witll ALWAYS be loaded, even if not needed.

In my case I want to show the original image if the optimization failed for some reason. If I would use placeholder, it would always load the original image as well. To only show the fallback, if an actual eerror happens, I created my own components, wrapping NuxtImg and NuxtPicture.

CustomNuxtImg.vue:

<template>
  <nuxt-img v-if="!hasError || !fallback" v-bind="propsWithoutFallbackAndSrc" :src="src" @error="handleError" @load="emit('load', $event)" />
  <img v-else v-bind="propsWithoutFallbackAndSrc" :src="fallback" />
</template>

<script lang="ts" setup>
import type { NuxtImgProps } from "@base/modules/image/types/nuxt-image"

/*
 * Small wrapper over NuxtImg
 * Supports fallback to default img tag when nuxt-img was unable to optimize the image.
 * This could happen if an image file is corrupted (e.g. wrongly saved by an image editing software)
 * However, providing a fallback is always a good practise
 */

interface Emits {
  (e: "error", data: any): void
  (e: "load", data: any): void
}

interface Props extends /* @vue-ignore */ NuxtImgProps {
  src?: string
  fallback?: string
}

const emit = defineEmits<Emits>()

const props = defineProps<Props>()

const attrs = useAttrs()

const propsWithoutFallbackAndSrc = computed(() => {
  const { fallback, src, ...restProps } = props

  return { ...attrs, ...restProps }
})

const hasError = ref(false)

const handleError = (data: any) => {
  emit("error", data)
  hasError.value = true
}
</script>

CustomNuxtPicture.vue:

<template>
  <nuxt-picture v-if="!hasError || !fallback" v-bind="propsWithoutFallbackAndSrc" :src="src" :imgAttrs="{ onError: handleError }" @load="emit('load', $event)" />
  <img v-else v-bind="propsWithoutFallbackAndSrc" :src="fallback" />
</template>

<script lang="ts" setup>
import type { NuxtPictureProps } from "@base/modules/image/types/nuxt-image"

/*
 * Small wrapper over NuxtPicture
 * Supports fallback to default img tag when nuxt-img was unable to optimize the image.
 * This could happen if an image file is corrupted (e.g. wrongly saved by an image editing software)
 * However, providing a fallback is always a good practise
 * */
interface Emits {
  (e: "error", data: any): void
  (e: "load", data: any): void
}

interface Props extends /* @vue-ignore */ NuxtPictureProps {
  src?: string
  fallback?: string
}

const emit = defineEmits<Emits>()

const props = defineProps<Props>()

const attrs = useAttrs()

const propsWithoutFallbackAndSrc = computed(() => {
  const { fallback, src, ...restProps } = props

  return { ...attrs, ...restProps }
})

const hasError = ref(false)

const handleError = (data: any) => {
  emit("error", data)
  hasError.value = true
}
</script>

Would really appreciate an official API.

Archetipo95 commented 6 months ago

@danielroe I was just looking for the same feature. Here in the Nuxt Movies example there is exactly a use case where the HD image is not present and we could use the SD one as fallback. Go under the videos tab here: https://movies.nuxt.space/tv/63770

/youtube/vi/nWufb0qL1xU/maxresdefault.jpg -> 404 ❌ /youtube/vi/nWufb0qL1xU/hqdefault.jpg -> 200 ✅

This is the componente Card.vue

<script setup lang="ts">
import type { Video } from '~/types'

const props = defineProps<{
  item: Video
}>()

const showModal = useIframeModal()
function play() {
  return showModal(getVideoLink(props.item)!)
}
</script>

<template>
  <button pb2 text-left @click="play()">
    <div
      block bg-gray4:10 p1 flex
      class="aspect-16/9"
      transition duration-400 relative
      hover="scale-102 z10"
    >
      <NuxtImg
        :src="`/youtube/vi/${item.key}/maxresdefault.jpg`"
        width="400"
        height="600"
        format="webp"
        :alt="props.item.name"
        w-full h-full object-cover
      />
      <div flex w-full h-full absolute inset-0 op20 hover:op100 transition>
        <div i-ph-play ma text-3xl />
      </div>
    </div>
    <div mt-2>
      {{ props.item.name }}
    </div>
    <div op60 text-sm>
      {{ props.item.type }}
    </div>
  </button>
</template>
Pausejo commented 1 week ago

@danielroe , I was excited to see the solution working, but it's not :(

Image