Skip to content

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.

ts
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

OptionTypeDefaultDescription
srcstring | SrcSetImage URL or format object
widthsnumber[][]Widths for width-based (w) srcset generation
densitiesnumber[] | Record<number, string>Density descriptors (1x/2x/3x); list reuses src, map gives distinct files; takes precedence over widths, ignores sizes
sizesstringsizes attribute value (width-based srcset only)
lazybooleantrueEnable IntersectionObserver
rootMarginstring"200px"IO rootMargin
thresholdnumber0IO threshold
fitObjectFit"cover"object-fit style
maxRetriesnumber0Max retry attempts on load failure
retryDelaynumber1000Initial delay in ms; doubles each retry

State machine

idle  →  loading  →  loaded
                  →  error
  • When lazy: true — transitions to loading when the observed element enters the viewport
  • When lazy: false — transitions to loading immediately after onMounted

Return value

PropertyTypeDescription
statusRef<ImageStatus>Current loading state
isLoadedComputedRef<boolean>true when status === 'loaded'
isErrorComputedRef<boolean>true when status === 'error'
imgAttrsComputedRef<object>{ src, srcset?, sizes?, style } — ready for v-bind
observeFunctionPass a Ref<HTMLElement> to start watching for intersection
onImgLoadFunctionCall from <img @load> to advance to loaded
onImgErrorFunctionCall from <img @error> to advance to error

Example — custom render

vue
<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.

vue
<!-- 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

OptionTypeDefaultDescription
srcstringURL of the background image
placeholderstringBase64 or URL shown immediately; replaced on load
rootMarginstring"200px"IO rootMargin
thresholdnumber0IO threshold
onLoad() => voidCalled when the image finishes loading
onError(e: Event) => voidCalled when the image fails to load

Behaviour

  1. On mount — creates an IntersectionObserver and starts watching the element
  2. When the element enters the viewport — if placeholder is set it is applied immediately as background-image
  3. A new Image object loads src in the background
  4. On load — background-image is updated to src; onLoad is called
  5. On error — onError is called; background-image stays as the placeholder (if any)
  6. On unmount — the observer is disconnected
  7. 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:

vue
<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:

ts
import { vLazyImg } from 'vue-image-kit'

app.directive('lazy-img', vLazyImg)

Example — card with lazy background

vue
<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.

vue
<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

OptionTypeDefaultDescription
placeholderstringURL/data URL shown (blurred) until the full image loads
densitiesnumber[]Builds a responsive image-set() with 1x/2x/… entries
typestringMIME hint for image-set() entries (e.g. 'image/webp')
lazybooleantrueGate loading behind IntersectionObserver
rootMarginstring'200px'IO root margin
thresholdnumber0IO threshold
transitionstring'0.4s ease'Blur-up transition
backgroundSizestring'cover'background-size
backgroundPositionstring'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:

ts
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 importing
  • v-lazy-img directive is registered and available in all templates

Import the plugin and individual exports separately if needed:

ts
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'