Vue.js plugin for lazy-loading images and components with directives, components, and programmatic APIs
—
Vue components for lazy loading content and images with full Vue 3 composition API support.
A wrapper component that lazily renders its content when it becomes visible in the viewport.
/**
* Lazy component that renders content only when visible
*/
interface LazyComponentProps {
/** HTML tag to render as wrapper (default: 'div') */
tag?: string;
}
/**
* Events emitted by lazy-component
*/
interface LazyComponentEvents {
/** Emitted when component becomes visible and content is shown */
show: (visible: boolean) => void;
}
/**
* Slots available in lazy-component
*/
interface LazyComponentSlots {
/** Default slot content that will be lazily rendered */
default: () => VNode[];
}Usage Examples:
<template>
<!-- Basic lazy component -->
<lazy-component @show="onComponentShow">
<img src="/heavy-image.jpg" alt="Heavy image">
<p>This content loads when visible</p>
</lazy-component>
<!-- Custom wrapper tag -->
<lazy-component tag="section" @show="onSectionShow">
<h2>Lazy Section</h2>
<video src="/large-video.mp4" controls></video>
</lazy-component>
<!-- In a list -->
<div class="image-gallery">
<lazy-component
v-for="(item, index) in galleryItems"
:key="item.id"
@show="() => onItemShow(index)"
>
<img :src="item.imageUrl" :alt="item.title">
<h3>{{ item.title }}</h3>
</lazy-component>
</div>
</template>
<script setup>
import { ref } from "vue";
const galleryItems = ref([
{ id: 1, imageUrl: "/gallery1.jpg", title: "Image 1" },
{ id: 2, imageUrl: "/gallery2.jpg", title: "Image 2" },
{ id: 3, imageUrl: "/gallery3.jpg", title: "Image 3" },
]);
const onComponentShow = (visible) => {
console.log("Component is now visible:", visible);
};
const onSectionShow = (visible) => {
console.log("Section became visible:", visible);
};
const onItemShow = (index) => {
console.log(`Gallery item ${index} is now visible`);
};
</script>A specialized component for lazy loading images with built-in state management and error handling.
/**
* Lazy image component with built-in loading states
*/
interface LazyImageProps {
/** Image source URL or configuration object */
src: string | VueLazyloadImageOptions;
/** HTML tag to render (default: 'img') */
tag?: string;
}
/**
* Image options for lazy-image component
*/
interface VueLazyloadImageOptions {
/** Image source URL */
src: string;
/** Error fallback image URL */
error?: string;
/** Loading placeholder image URL */
loading?: string;
/** Maximum loading attempts */
attempt?: number;
}
/**
* Slots available in lazy-image component
*/
interface LazyImageSlots {
/** Optional slot content rendered alongside image */
default?: () => VNode[];
}Usage Examples:
<template>
<!-- Basic lazy image -->
<lazy-image src="/photo.jpg" />
<!-- Image with configuration -->
<lazy-image :src="{
src: '/high-res-photo.jpg',
loading: '/loading-placeholder.png',
error: '/error-fallback.png',
attempt: 3
}" />
<!-- Custom wrapper tag -->
<lazy-image
tag="figure"
:src="imageConfig"
>
<figcaption>Image caption content</figcaption>
</lazy-image>
<!-- Dynamic image source -->
<lazy-image
:src="computedImageSrc"
@load="onImageLoaded"
@error="onImageError"
/>
</template>
<script setup>
import { computed, ref } from "vue";
const imageConfig = ref({
src: "/portrait.jpg",
loading: "/skeleton-loader.svg",
error: "/broken-image.svg",
attempt: 5,
});
const imageQuality = ref("medium");
const imageName = ref("landscape");
const computedImageSrc = computed(() => ({
src: `/images/${imageName.value}-${imageQuality.value}.jpg`,
loading: "/placeholders/image-loading.svg",
error: "/placeholders/image-error.svg",
}));
const onImageLoaded = () => {
console.log("Lazy image successfully loaded");
};
const onImageError = () => {
console.error("Failed to load lazy image");
};
</script>Advanced usage patterns for integrating lazy components with other Vue features.
With Vue Router:
<template>
<router-view v-slot="{ Component }">
<lazy-component @show="onRouteComponentShow">
<component :is="Component" />
</lazy-component>
</router-view>
</template>
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
const onRouteComponentShow = () => {
console.log("Route component visible:", route.path);
};
</script>With Suspense:
<template>
<Suspense>
<template #default>
<lazy-component @show="onAsyncComponentShow">
<AsyncComponent />
</lazy-component>
</template>
<template #fallback>
<div class="loading-placeholder">Loading async component...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const AsyncComponent = defineAsyncComponent(() => import("./HeavyComponent.vue"));
const onAsyncComponentShow = () => {
console.log("Async component is now visible");
};
</script>With Teleport:
<template>
<lazy-component @show="onModalContentShow">
<Teleport to="#modal-container">
<div class="modal-content">
<lazy-image :src="modalImageSrc" />
</div>
</Teleport>
</lazy-component>
</template>
<script setup>
const modalImageSrc = ref("/modal-background.jpg");
const onModalContentShow = () => {
console.log("Modal content is visible");
};
</script>Optimizing lazy component performance for large lists and complex layouts.
Virtual Scrolling Integration:
<template>
<div class="virtual-list" ref="scrollContainer">
<lazy-component
v-for="item in visibleItems"
:key="item.id"
@show="() => trackItemView(item.id)"
>
<ItemComponent :item="item" />
</lazy-component>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const scrollContainer = ref();
const allItems = ref([/* large array of items */]);
// Only render items in viewport + buffer
const visibleItems = computed(() => {
// Implementation for virtual scrolling logic
return allItems.value.slice(startIndex.value, endIndex.value);
});
const trackItemView = (itemId) => {
// Analytics tracking when item becomes visible
console.log("Item viewed:", itemId);
};
</script>Intersection Observer Configuration:
<template>
<lazy-component
@show="onHighPriorityShow"
data-lazy-priority="high"
>
<CriticalContent />
</lazy-component>
<lazy-component
@show="onLowPriorityShow"
data-lazy-priority="low"
>
<OptionalContent />
</lazy-component>
</template>
<script setup>
// Components automatically inherit observer settings from plugin config
// Can be customized via global configuration during plugin installation
const onHighPriorityShow = () => {
// Handle high priority content visibility
};
const onLowPriorityShow = () => {
// Handle low priority content visibility
};
</script>Managing component states and lifecycle hooks.
<template>
<lazy-component @show="handleComponentShow">
<div class="content-wrapper">
<lazy-image
:src="imageSource"
@loading="onImageLoading"
@loaded="onImageLoaded"
@error="onImageError"
/>
<div class="content-text">
{{ contentText }}
</div>
</div>
</lazy-component>
</template>
<script setup>
import { ref, nextTick } from "vue";
const imageSource = ref("/initial-image.jpg");
const contentText = ref("Initial content");
const isComponentVisible = ref(false);
const handleComponentShow = async (visible) => {
isComponentVisible.value = visible;
if (visible) {
// Load additional resources when component becomes visible
await loadAdditionalResources();
}
};
const onImageLoading = () => {
console.log("Image started loading");
};
const onImageLoaded = () => {
console.log("Image finished loading");
// Trigger any post-load animations or updates
nextTick(() => {
// DOM updates after image load
});
};
const onImageError = () => {
console.error("Image failed to load");
// Fallback behavior
imageSource.value = "/fallback-image.jpg";
};
const loadAdditionalResources = async () => {
// Fetch additional data when component is visible
const response = await fetch("/api/additional-content");
const data = await response.json();
contentText.value = data.text;
};
</script>Install with Tessl CLI
npx tessl i tessl/npm-vue-lazyload