A Vue 3 lazy render component based on the IntersectionObserver API. Components are mounted only when they enter the viewport, and updates are frozen when they leave, optimizing page performance.
- 🚀 Lazy Mount - Components are mounted only when they enter the viewport
- ❄️ Update Freezing - Automatically freezes component updates when leaving the viewport
- 📦 Lightweight - Zero dependencies, small bundle size
npm install vuedefer
# or
pnpm add vuedefer
# or
yarn add vuedefer<script setup lang="ts">
import { LazyRender } from 'vuedefer'
</script>
<template>
<LazyRender>
<HelloWorld />
<template #fallback>
<div class="placeholder">Loading...</div>
</template>
</LazyRender>
</template>Components are mounted and rendered only when they enter the viewport:
<script setup lang="ts">
import { ref } from 'vue'
import { LazyRender } from 'vuedefer'
import HelloWorld from './HelloWorld.vue'
const isMounted = ref(false)
</script>
<template>
<div class="container">
<!-- Status indicator -->
<div class="status" :class="{ mounted: isMounted }">
{{ isMounted ? '✅ Mounted' : '⏳ Not mounted' }}
</div>
<!-- Spacer to simulate scrolling -->
<div style="height: 100vh;">
<p>👇 Scroll down to see lazy loading in action...</p>
</div>
<!-- Lazy loaded component -->
<LazyRender>
<HelloWorld @vue:mounted="isMounted = true" />
<template #fallback>
<div class="placeholder">Loading...</div>
</template>
</LazyRender>
</div>
</template>Specify a custom scroll container and trigger margin:
<script setup lang="ts">
import { ref } from 'vue'
import { LazyRender } from 'vuedefer'
const scrollContainer = ref<HTMLElement | null>(null)
</script>
<template>
<div ref="scrollContainer" class="scroll-container">
<LazyRender :root="scrollContainer" root-margin="100px">
<ExpensiveChart />
<template #fallback>
<div class="skeleton">Loading chart...</div>
</template>
</LazyRender>
</div>
</template>Use threshold to control how much of the component must be visible to trigger rendering:
<template>
<!-- Trigger rendering when 50% of the component is visible -->
<LazyRender :threshold="0.5">
<VideoPlayer />
<template #fallback>
<div class="video-placeholder">Preparing video...</div>
</template>
</LazyRender>
</template><template>
<!-- Support multiple thresholds -->
<LazyRender :threshold="[0, 0.25, 0.5, 0.75, 1]">
<ProgressiveImage />
</LazyRender>
</template><template>
<!-- Use section as the wrapper element -->
<LazyRender tag="section">
<ArticleContent />
<template #fallback>
<div class="article-skeleton" />
</template>
</LazyRender>
</template>| Name | Type | Default | Description |
|---|---|---|---|
tag |
string |
'div' |
HTML tag name for the wrapper element |
root |
Element | Document | ShadowRoot | null |
null |
The root element for IntersectionObserver. null uses the browser viewport |
rootMargin |
string |
undefined |
Margin around the root element to expand or shrink the detection area. Same format as CSS margin, e.g., '10px', '10px 20px' |
threshold |
number | number[] |
undefined |
Visibility ratio threshold(s) to trigger the callback. 0 triggers when a single pixel is visible, 1 triggers when fully visible |
| Name | Description |
|---|---|
default |
Default slot for the component content to be lazily loaded |
fallback |
Placeholder content displayed before the component enters the viewport |
- Initial State: When the component is not in the viewport, the
fallbackslot content is rendered as a placeholder - Entering Viewport: When the component enters the viewport (detected by IntersectionObserver), the actual component in the
defaultslot is mounted and rendered - Leaving Viewport: When the component leaves the viewport, its
renderfunction is replaced to return the currentsubTree, freezing updates and avoiding unnecessary re-renders - Re-entering: When the component re-enters the viewport, the original
renderfunction is restored, allowing the component to respond to data changes and re-render
MIT