@salvatorecervone/viewportlazy
v2.1.0
Published
High-performance Vue 3 component, composable, and directive to lazy load elements when entering viewport
Maintainers
Readme

@salvatorecervone/viewportlazy
High-performance Vue 3 component, composable, and directive to lazy load elements, components, and media when entering the viewport using
IntersectionObserver.
Features
- ⚡ Lightweight & Fast: Built on native
IntersectionObserverwith automatic cleanup. - 🧩 3 Ways to Use:
- Component:
<ViewPortLazy>with slot#placeholdersupport (Zero CLS). - Composable:
useViewportLazy(elementRef, options). - Directive:
v-viewport-lazy.
- Component:
- 📐 Zero Layout Shift (CLS): Reserve height with
min-heightand skeletons before content loads. - ⏱️ Flexible Delays: Configure debounce/throttle delays before triggering.
- 🎯 Configurable Root & Margin: Fine-tune
rootMarginandthreshold. - 🔷 TypeScript Ready: Complete TypeScript definitions included.
Installation
npm install @salvatorecervone/viewportlazyQuick Start
1. Global Plugin Registration
import { createApp } from 'vue';
import App from './App.vue';
import ViewPortLazy from '@salvatorecervone/viewportlazy';
const app = createApp(App);
app.use(ViewPortLazy); // Registers <ViewPortLazy> component and v-viewport-lazy directive
app.mount('#app');Usage
Option A: <ViewPortLazy> Component
Basic Usage
<template>
<ViewPortLazy>
<HeavyChartComponent />
</ViewPortLazy>
</template>
<script setup>
import { ViewPortLazy } from '@salvatorecervone/viewportlazy';
</script>With #placeholder Slot (Zero Layout Shift)
<template>
<ViewPortLazy :min-height="300" root-margin="100px" @visible="onVisible">
<!-- Rendered when entering viewport -->
<HeavyCard :data="cardData" />
<!-- Rendered before entering viewport (Skeleton loader) -->
<template #placeholder>
<div class="skeleton-loader" style="height: 300px;">Loading...</div>
</template>
</ViewPortLazy>
</template>Component Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| tag | String | 'div' | Wrapper HTML tag |
| minHeight | String \| Number | null | Minimum height for wrapper before content loads |
| rootMargin | String | '0px' | Margin around root for IntersectionObserver (e.g. '100px') |
| threshold | Number \| Array | 0 | Intersection threshold(s) to trigger visibility |
| delayAllView | Number | 0 | Delay in milliseconds before marking as visible |
| delaySingle | Number | 0 | Additional single-instance delay in milliseconds |
| once | Boolean | true | If true, disconnects observer once visible |
Component Events
| Event | Payload | Description |
| --- | --- | --- |
| @visible | IntersectionObserverEntry | Emitted when element enters viewport |
| @hidden | IntersectionObserverEntry | Emitted when element exits viewport (when once: false) |
Option B: useViewportLazy Composable
For fine-grained logic in your script:
<template>
<div ref="targetEl" class="box">
<p v-if="isVisible">Data loaded on scroll! 🚀</p>
<p v-else>Waiting for viewport...</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { useViewportLazy } from '@salvatorecervone/viewportlazy';
const targetEl = ref(null);
const { isVisible, stop, start } = useViewportLazy(targetEl, {
rootMargin: '50px',
delay: 200,
once: true,
onVisible: () => {
console.log('Element is now visible! Fetching API data...');
}
});
</script>Option C: v-viewport-lazy Directive
For lightweight triggers directly in template:
<template>
<!-- Simple callback on intersect -->
<div v-viewport-lazy="onIntersect">...</div>
<!-- With options and delay -->
<div v-viewport-lazy="{ onVisible: loadImages, delay: 300, rootMargin: '100px' }">...</div>
</template>
<script setup>
function onIntersect() {
console.log('Element entered viewport!');
}
</script>Development & Playground
To run the local interactive playground and test changes:
npm run devTo run the automated tests:
npm run testTo build for production:
npm run buildChangelog
Please see CHANGELOG.md for more information on recent changes.
Contributing
Please see CONTRIBUTING.md for details on how to contribute.
License
The MIT License (MIT). Please see LICENSE.md for more information.
