Composables, Directive & Plugin
useImage
Headless composable. Use it when you need the loading state machine and computed attributes but want to render your own markup.
const {
status, // Ref<'idle' | 'loading' | 'loaded' | 'error'>
isLoaded, // ComputedRef<boolean>
isError, // ComputedRef<boolean>
imgAttrs, // ComputedRef<ImgAttrs> — ready to spread onto <img>
observe, // (el: Ref<HTMLElement | null>) => void
onImgLoad, // () => void — call from img @load
onImgError, // () => void — call from img @error
} = useImage(options)Options
| Option | Type | Default | Description |
|---|---|---|---|
src | string | SrcSet | — | Image URL or format object |
widths | number[] | [] | Widths for width-based (w) srcset generation |
densities | number[] | Record<number, string> | — | Density descriptors (1x/2x/3x); list reuses src, map gives distinct files; takes precedence over widths, ignores sizes |
sizes | string | — | sizes attribute value (width-based srcset only) |
lazy | boolean | true | Enable IntersectionObserver |
rootMargin | string | "200px" | IO rootMargin |
threshold | number | 0 | IO threshold |
fit | ObjectFit | "cover" | object-fit style |
maxRetries | number | 0 | Max retry attempts on load failure |
retryDelay | number | 1000 | Initial delay in ms; doubles each retry |
State machine
idle → loading → loaded
→ error- When
lazy: true— transitions toloadingwhen the observed element enters the viewport - When
lazy: false— transitions toloadingimmediately afteronMounted
Return value
| Property | Type | Description |
|---|---|---|
status | Ref<ImageStatus> | Current loading state |
isLoaded | ComputedRef<boolean> | true when status === 'loaded' |
isError | ComputedRef<boolean> | true when status === 'error' |
imgAttrs | ComputedRef<object> | { src, srcset?, sizes?, style } — ready for v-bind |
observe | Function | Pass a Ref<HTMLElement> to start watching for intersection |
onImgLoad | Function | Call from <img @load> to advance to loaded |
onImgError | Function | Call from <img @error> to advance to error |
Example — custom render
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useImage } from 'vue-image-kit'
const containerRef = ref<HTMLElement | null>(null)
const { status, isLoaded, imgAttrs, observe, onImgLoad, onImgError } = useImage({
src: '/photo.jpg',
widths: [400, 800, 1200],
sizes: '(max-width: 768px) 100vw, 50vw',
})
onMounted(() => {
observe(containerRef)
})
</script>
<template>
<div ref="containerRef" class="image-wrapper">
<div v-if="status === 'idle'" class="skeleton" />
<img
v-if="status === 'loading' || isLoaded"
v-bind="imgAttrs"
alt="Photo"
:class="{ visible: isLoaded }"
@load="onImgLoad"
@error="onImgError"
/>
<div v-if="status === 'error'" class="error-state">Failed to load</div>
</div>
</template>
<style scoped>
img {
opacity: 0;
transition: opacity 0.3s;
}
img.visible {
opacity: 1;
}
</style>vLazyImg
Directive for setting background-image on any element after it enters the viewport. Use it when you can't use the <VImage> component — CSS backgrounds, third-party wrappers, etc.
<!-- Simple string -->
<div v-lazy-img="'/background.jpg'" class="hero" />
<!-- Object with options -->
<div
v-lazy-img="{
src: '/background.jpg',
placeholder: 'data:image/jpeg;base64,...',
rootMargin: '100px',
onLoad: () => console.log('loaded'),
onError: (e) => console.error(e),
}"
class="hero"
/>Options
| Option | Type | Default | Description |
|---|---|---|---|
src | string | — | URL of the background image |
placeholder | string | — | Base64 or URL shown immediately; replaced on load |
rootMargin | string | "200px" | IO rootMargin |
threshold | number | 0 | IO threshold |
onLoad | () => void | — | Called when the image finishes loading |
onError | (e: Event) => void | — | Called when the image fails to load |
Behaviour
- On mount — creates an
IntersectionObserverand starts watching the element - When the element enters the viewport — if
placeholderis set it is applied immediately asbackground-image - A new
Imageobject loadssrcin the background - On load —
background-imageis updated tosrc;onLoadis called - On error —
onErroris called;background-imagestays as the placeholder (if any) - On unmount — the observer is disconnected
- On binding update — the observer is recreated with the new options
Registering the directive manually
The directive is registered automatically with VImageKitPlugin. To register it in a single component:
<script setup lang="ts">
import { vLazyImg } from 'vue-image-kit'
</script>
<template>
<div v-lazy-img="'/bg.jpg'" style="width:100%;height:400px" />
</template>Or globally without the plugin:
import { vLazyImg } from 'vue-image-kit'
app.directive('lazy-img', vLazyImg)Example — card with lazy background
<script setup lang="ts">
import { vLazyImg } from 'vue-image-kit'
const cards = [
{ id: 1, bg: '/card-1.jpg', placeholder: 'data:image/jpeg;base64,/9j/...' },
{ id: 2, bg: '/card-2.jpg', placeholder: 'data:image/jpeg;base64,/9j/...' },
]
</script>
<template>
<div
v-for="card in cards"
:key="card.id"
v-lazy-img="{ src: card.bg, placeholder: card.placeholder }"
class="card"
/>
</template>
<style scoped>
.card {
width: 300px;
height: 200px;
background-size: cover;
background-position: center;
border-radius: 12px;
}
</style>useBackgroundImage
The v-lazy-img directive lazy-loads a background but can't do srcset. useBackgroundImage is the composable counterpart: lazy loading + responsive image-set() (the CSS-native equivalent of srcset) + blur-up — returned as a reactive :style you bind yourself.
<script setup lang="ts">
import { useBackgroundImage } from 'vue-image-kit'
const { target, style, isLoaded } = useBackgroundImage('/hero.jpg', {
placeholder: 'data:image/jpeg;base64,/9j/...',
densities: [1, 2], // → image-set(url("/hero.jpg") 1x, url("/hero.jpg") 2x)
rootMargin: '300px',
})
</script>
<template>
<section ref="target" :style="style" class="hero">
<h1 v-show="isLoaded">Welcome</h1>
</section>
</template>
<style scoped>
.hero {
width: 100%;
height: 60vh;
}
</style>Options
| Option | Type | Default | Description |
|---|---|---|---|
placeholder | string | — | URL/data URL shown (blurred) until the full image loads |
densities | number[] | — | Builds a responsive image-set() with 1x/2x/… entries |
type | string | — | MIME hint for image-set() entries (e.g. 'image/webp') |
lazy | boolean | true | Gate loading behind IntersectionObserver |
rootMargin | string | '200px' | IO root margin |
threshold | number | 0 | IO threshold |
transition | string | '0.4s ease' | Blur-up transition |
backgroundSize | string | 'cover' | background-size |
backgroundPosition | string | 'center' | background-position |
Returns { target, style, status, isLoaded, isLoading, load }. Attach target via a template ref and bind style; call load() to trigger manually when lazy: false. SSR-safe (loading is deferred to the client).
Vue plugin
Register <VImage> and v-lazy-img globally with a single app.use() call:
import { createApp } from 'vue'
import { VImageKitPlugin } from 'vue-image-kit'
import App from './App.vue'
const app = createApp(App)
app.use(VImageKitPlugin)
app.mount('#app')After installation:
<VImage>is available in all templates without importingv-lazy-imgdirective is registered and available in all templates
Import the plugin and individual exports separately if needed:
import {
VImageKitPlugin, // Vue plugin
VImage, // component
vLazyImg, // directive
useImage, // composable
useBlurhash, // canvas composable
useLazyLoad, // IO composable
decodeBlurhash, // standalone decoder
generateSrcset, // srcset utility
generateSizes, // sizes utility
} from 'vue-image-kit'