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
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";
}
});
},
};