Skip to content

Image Fallback Directive

The v-image-fallback directive is used to display a fallback image if the original image fails to load. It can also loop through a list of fallback images until one of them loads successfully.

Usage

Image
Image
Image
View Code
vue
<script setup>
import { ref } from "vue";
const value = `<svg id='vLoader' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><filter id="svgSpinnersGooeyBalls20"><feGaussianBlur in="SourceGraphic" result="y" stdDeviation="1"/><feColorMatrix in="y" result="z" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7"/><feBlend in="SourceGraphic" in2="z"/></filter></defs><g filter="url(#svgSpinnersGooeyBalls20)"><circle cx="5" cy="12" r="4" fill="currentColor"><animate attributeName="cx" calcMode="spline" dur="2s" keySplines=".36,.62,.43,.99;.79,0,.58,.57" repeatCount="indefinite" values="5;8;5"/></circle><circle cx="19" cy="12" r="4" fill="currentColor"><animate attributeName="cx" calcMode="spline" dur="2s" keySplines=".36,.62,.43,.99;.79,0,.58,.57" repeatCount="indefinite" values="19;16;19"/></circle><animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></g></svg>`;
const images = ref([
  {
    loaderOptions: {
      width: "4rem",
      height: "4rem",
    },
    src: "https://images.pexels.com/photos/5091121/pexels-photo-5091121.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
    fallbacks: [
      "https://images.pexels.com/photo/207789/pexels-photo-207789.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
      "http://placeskull.com/202/202",
    ],
  },
  {
    loaderOptions: {
      width: "6rem",
      height: "6rem",
    },
    src: "https://images.pexels.com/photos/614810/pexels-photo-614810.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
    fallbacks: [
      "https://images.pexels.com/photo/207789/pexels-photo-207789.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
      "http://placeskull.com/202/202",
    ],
  },
  {
    loaderOptions: {
      width: "3rem",
      height: "3rem",
      value: value,
    },
    src: "https://images.unsplash.com/photos-1464822759023-fed622ff2c3b?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    fallbacks: [
      "https://images.pexels.com/photo/207789/pexels-photo-207789.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
      "https://placehold.it/200x200?text=Fallback",
    ],
  },
]);
</script>
<template>
  <div class="w-full flex flex-col justify-center items-center">
    <div class="grid grid-cols-3 gap-4">
      <div
        v-for="(image, index) in images"
        :key="index"
        class="w-32 h-32 flex justify-center items-center rounded-full object-cover object-center bg-gray-100 dark:bg-zinc-800"
      >
        <img
          v-image-fallback="{
            loaderOptions: image.loaderOptions,
            fallbacks: image.fallbacks,
          }"
          class="w-32 h-32 rounded-full object-cover object-center"
          :src="image.src"
          alt="Image"
        />
      </div>
    </div>
  </div>
</template>
<style scoped></style>

Arguments

The v-image-fallback does not take any arguments.

Modifiers

The v-image-fallback does not take any modifiers.

Source Code

js
const loader = `<svg id='vLoader' xmlns="http://www.w3.org/2000/svg" class='animate-spin' viewBox="0 0 16 16"><path fill="currentColor" d="M2 8a6 6 0 1 1 6 6a.5.5 0 0 0 0 1a7 7 0 1 0-7-7a.5.5 0 0 0 1 0Z"/></svg>`;

export const VImageFallback = {
  mounted(el, binding) {
    el.classList.add("hidden"); // Hide the image until it loads

    // Set loader options
    const loaderOptions = binding.value && binding.value.loaderOptions;

    // Insert the loader (either custom or default) before the image
    if (loaderOptions && loaderOptions.value) {
      el.insertAdjacentHTML("beforebegin", loaderOptions.value);
      const customLoader = document.getElementById("vLoader");
      customLoader.style.width = loaderOptions.width || "32px";
      customLoader.style.height = loaderOptions.height || "32px";
    } else {
      el.insertAdjacentHTML("beforebegin", loader);
      const defaultLoader = document.getElementById("vLoader");
      defaultLoader.style.width = loaderOptions.width || "32px";
      defaultLoader.style.height = loaderOptions.height || "32px";
    }

    el.addEventListener("load", () => {
      // Remove the loading state if an image successfully loads
      const loaderElement = document.getElementById("vLoader");
      if (loaderElement) {
        loaderElement.remove();
      }
      el.classList.remove("hidden");
    });

    el.addEventListener("error", () => {
      // Set fallback image if provided, otherwise use a default placeholder
      const fallbacks = binding.value && binding.value.fallbacks;

      if (fallbacks && fallbacks.length > 0) {
        // Loop through the fallback images
        let fallbackIndex = 0;

        const tryFallback = () => {
          el.src = fallbacks[fallbackIndex];

          // Check if the current fallback image loaded successfully
          el.addEventListener("load", () => {
            el.classList.remove("hidden"); // Show the image
          });

          el.addEventListener("error", () => {
            // If the current fallback fails to load, try the next one
            fallbackIndex++;
            if (fallbackIndex < fallbacks.length) {
              tryFallback();
            } else {
              // If none of the fallbacks load, set the source to a default placeholder
              el.src = "https://placehold.it/200x200?text=404";
            }
          });
        };

        // Start trying the fallbacks
        tryFallback();
      } else {
        // If no fallbacks are provided, set the source to a default placeholder
        el.src = "https://placehold.it/200x200?text=404";
      }
    });
  },
};