Vue Infinity is a lightweight library for creating resource efficient apps. Unload parts of your UI when not visible and build easy to layout virtual scrollers that can present unlimited content while keeping resource usage fixed.
Build lightning-fast Vue apps that only render what is on-screen
Vue-Infinity brings a radical efficiency boost to your UI by applying the same principle that powers 3D engines: if it’s not in visible, it doesn’t get rendered. This lets your app handle hundreds or thousands of elements without bloating memory, janking or killing batteries.
Whether you’re building infinite feeds, carousels, media galleries, or dashboards—Vue-Infinity keeps your app fast, smooth, and efficient.
The Ghost
component optimizes performance by conditionally rendering its slot content. When off-screen, the content is replaced by a dimensionally-identical placeholder, “unloading” heavy elements (like videos) while preserving layout.
on-load
: Fired when the component’s content becomes visible and is rendered.before-unload
: Fired in the same tick that the component’s content starts to become hidden.on-unload
: Fired in the next tick after the component’s content has become hidden and replaced by the placeholder.Example:
<Ghost @on-load="handleLoad" @before-unload="handleBeforeUnload" @on-unload="handleUnload">
<div style="height: 300px; background-color: lightblue;">
This content will be replaced when not visible.
</div>
</Ghost>
The v-ghost
directive offers a lightweight, flexible alternative to the Ghost
component for optimizing performance. By applying v-ghost
to any element, you can ensure its content is only rendered when it enters the viewport. When off-screen, the element’s content is replaced by a placeholder, preserving layout integrity while freeing up system resources.
onLoad
, beforeUnload
, and onUnload
callbacks for fine-grained control over the element’s lifecycle.Basic Example (Video):
<template>
<video controls muted playsinline v-ghost>
<source src="your-video.mp4" type="video/mp4" />
</video>
</template>
Advanced Example (All Options):
<template>
<div v-ghost="{
rootMargin: '100px',
onLoad: handleLoad,
beforeUnload: handleBeforeUnload,
onUnload: handleUnload
}">
<!-- Heavy content goes here -->
</div>
</template>
<script setup>
const handleLoad = () => {
console.log('Content is now visible and rendered.');
};
const handleBeforeUnload = () => {
console.log('Content is about to be hidden.');
};
const handleUnload = () => {
console.log('Content is hidden and replaced by a placeholder.');
};
</script>
A general-purpose virtual scroll component optimized for grid-like or carousel-based layouts.
InfiniteList
for managing data accessonGetItemAspectRatio
callback function that returns the aspect ratio of an item.Example:
<template>
<InfiniteCarousel
:infinite-list="infiniteList"
:height="'50vh'"
:width="'100%'"
:numColsToShow="3"
:numRowsToShow="2"
>
<template #item="{ item, index }">
<img :src="item.url" :alt="item.title" class="carousel-img"/>
</template>
</InfiniteCarousel>
</template>
<script setup>
import { useInfiniteList } from 'vue-infinity';
const infiniteList = useInfiniteList({
fetchItems: (page) => fetchPage(page),
itemsPerPage: 20,
maxPagesToCache: 5
});
</script>
Example with Dynamic Item Sizing:
<template>
<InfiniteCarousel
:infinite-list="infiniteList"
:height="'60vh'"
:width="'100%'"
:numColsToShow="4"
:numRowsToShow="3"
:onGetItemAspectRatio="getItemAspectRatio"
>
<template #item="{ item }">
<img :src="item.url" :alt="item.title" class="carousel-img"/>
</template>
</InfiniteCarousel>
</template>
<script setup>
import { useInfiniteList } from 'vue-infinity';
const fetchItems = async (page) => {
// Replace with your actual data fetching logic
const response = await fetch(`/api/items?page=${page}`);
return response.json();
};
const infiniteList = useInfiniteList({
fetchItems,
itemsPerPage: 30, // Adjust based on your data
maxPagesToCache: 5
});
const getItemAspectRatio = (item) => {
// Assuming item has width and height properties, or can be parsed from URL
if (!item || !item.url) {
return 1; // Default to square if aspect ratio cannot be determined
}
try {
const urlParts = item.url.split('/');
const width = parseInt(urlParts[urlParts.length - 2], 10);
const height = parseInt(urlParts[urlParts.length - 1].split('?')[0], 10);
return width / height;
} catch (e) {
console.error("Error parsing item aspect ratio:", e);
return 1;
}
};
</script>
Provides reactive, paginated access to large datasets with full type support.
AbortController
Example:
const { pages, getItem, fetchPage } = useInfiniteList({
fetchItems: async (page, signal) => {
const response = await fetch(`/api/items?page=${page}`, { signal });
return response.json();
},
itemsPerPage: 50,
maxPagesToCache: 5
});
Enhances the native IntersectionObserver
by automatically handling new elements and cleaning up removed ones.
Example:
const containerRef = ref<HTMLElement>();
const { disconnect } = useAutoObserver(
containerRef,
(entries) => {
entries.forEach(entry => {
console.log('Element visibility changed:', entry.isIntersecting);
});
},
{
rootMargin: '200px',
threshold: 0.1,
filter: el => el.classList.contains('observe-me')
}
);
npm install vue-infinity
Explore the live demo here: https://tewolde.co/vueInfinity/
To run the playground application locally:
npm run playground
v-ghost
directive to optimize performance by automatically unloading off-screen content.InfiniteCarousel
now supports an onGetItemAspectRatio
callback, enabling it to render items with variable heights.v-ghost
directive and the dynamic sizing feature.Apache 2.0 License - https://opensource.org/licenses/Apache-2.0