Search code examples
javascriptvue.jsdynamicv-for

How to skip URL image on 404 Error in Frontend


I would like to implement images dynamically (image_url_1.jpg - image_url_5.jpg) based on a URL. Everything works well but if the case occurs that e.g. "image_url_4.jpg" is not available (404 Error), the symbol for broken images is always displayed - logically.

Is there a possibility to check in my v-for on error 404 or if the image could be displayed? And if not skip it with a v-if for example.

I have tried it with require(url) but that's not working for me.

Code example

<template>
  <div v-for="(n, index) in 5" :key="index">
    <img :src="'image_url_' + n + '.jpg'" />
  </div>
</template>

Visualization with a picture:

enter image description here


Solution

  • An HTML element has a error event: (MDN Web Docs)

    The error event is fired on an element when a resource failed to load, or can't be used. For example, if a script has an execution error or an image can't be found or is invalid.

    It's easy to implement it: (demo)

    <script setup lang="ts">
    import { ref } from 'vue';
    
    const num = 4;
    const show = ref(new Array(num + 1).fill(true)); // using "of" in v-for, index starts from 1
    
    function handleError(index) {
      show.value[index] = false;
    }
    </script>
    
    <template>
      <template v-for="index of num">
        <div v-if="show[index]">
          <img :src="`/images/${index}.png`" @error="handleError(index)" />
        </div>
      </template>
    </template>
    

    If you're using Vite and images are in the project (can be imported as URL), there is another method with better performance: (demo)

    <script setup lang="ts">
    function getImages() {
      // All the images are under `./images` and end with `.png`
      const original = import.meta.glob('./images/*.png', {
        eager: true,
        import: 'default',
      });
      // the structure of `original` is like:
      // { './images/1.png': '/path/to/real/resource', ... }
    
      const transformed = Object.entries(original)
        // './images/1.png' -> 1
        .map(([key, value]) => [
          key.replace('./images/', '').replace('.png', ''),
          value,
        ]);
    
      return Object.fromEntries(transformed); // { 1: '/path/to/real/resource', ... }
    }
    
    const images = getImages();
    </script>
    
    <template>
      <div v-for="(path, index) in images" :key="index">
        <img :src="path" />
      </div>
    </template>
    

    The first method is to try to load each image at runtime and then exclude items that fail to load.

    The second method is to import the URLs of all images at compile time and show the images according to the actual URLs. Thus, the images are determined at build time, leading to a better performance.