Skip to content

Справочник

Типы TypeScript

Все публичные типы экспортируются из корня пакета:

ts
import type {
  ImageStatus, // 'idle' | 'loading' | 'loaded' | 'error'
  SrcSet, // { avif?: string; webp?: string; fallback: string }
  ResponsiveSrc, // Record<string, string | SrcSet> — брейкпоинт → URL, либо набор форматов для этого брейкпоинта
  BreakpointMap, // Record<string, string> — брейкпоинт → CSS media query
  VImageKitOptions, // { breakpoints?: BreakpointMap }
  LazyImgOptions, // { src, placeholder?, rootMargin?, threshold?, onLoad?, onError? }
  ObjectFit, // 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
  FocalPoint, // { x: number; y: number } — доли 0–1
  Densities, // number[] | Record<number, string> — дескрипторы плотности
  ImageMeta, // форма записи манифеста CLI / импорта `?vik`, для пропа `image`
  Layout, // 'fixed' | 'responsive' | 'fill' — проп `layout`
} from 'vue-image-kit'

ImageStatus

ts
type ImageStatus = 'idle' | 'loading' | 'loaded' | 'error'

Конечный автомат переходит по порядку: idle → loading → loaded или idle → loading → error.

SrcSet

ts
interface SrcSet {
  avif?: string // Опциональный URL AVIF-источника
  webp?: string // Опциональный URL WebP-источника
  fallback: string // Обязателен — используется как fallback для <img src>
}

LazyImgOptions

ts
interface LazyImgOptions {
  src: string
  placeholder?: string
  rootMargin?: string
  threshold?: number
  onLoad?: () => void
  onError?: (e: Event) => void
}

Директива v-lazy-img принимает либо обычную string (это src), либо объект LazyImgOptions.

Работа с типизированными опциями в v-lazy-img

ts
import type { LazyImgOptions } from 'vue-image-kit'

const bgOptions: LazyImgOptions = {
  src: '/hero.jpg',
  placeholder: 'data:image/jpeg;base64,...',
  rootMargin: '100px',
  onLoad: () => analytics.track('hero_loaded'),
}
vue
<div v-lazy-img="bgOptions" class="hero" />

Совместимость с SSR

СценарийПоведение
Серверный рендер — <VImage>Рендерит <img loading="lazy"> с src и alt; без IO, без canvas
Серверный рендер — aspect-ratioРендерится <div> с aspect-ratio: width/height, когда заданы width и height
Blurhash на сервереКод canvas внутри onMounted — не выполняется; вместо этого рендерится пустой контейнер
IntersectionObserver на сервереНе используется; сервер рендерит обычный <img>
ГидратацияПосле монтирования onMounted настраивает IO (если lazy: true) либо сразу начинает загрузку (если lazy: false)
v-lazy-img на сервереХуки директивы (mounted, unmounted) не вызываются во время SSR — IO не создаётся
useLazyLoad на сервереНемедленно возвращает { isIntersecting: true } — вызывающий код действует так, будто уже во вьюпорте

Использование в Nuxt:

Особая настройка не требуется. Компонент корректно рендерится и в SSR, и в клиентском режиме. Если нужно узнать, смонтировался ли клиент, используйте onMounted из Vue:

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const mounted = ref(false)
onMounted(() => {
  mounted.value = true
})
</script>

<template>
  <VImage v-if="mounted" src="/photo.jpg" alt="Photo" blurhash="..." />
  <div v-else style="aspect-ratio: 16/9; background: #e5e7eb;" />
</template>

Архитектура

VImage.vue
│  props: src, alt, width, height,
│         blurhash, thumbhash, placeholder,
│         widths, sizes, sources, breakpoints,
│         lazy, rootMargin, threshold, fit,
│         maxRetries, retryDelay,
│         fetchpriority, decoding

├──▶ useImage(options)
│         │
│         ├── useLazyLoad({ rootMargin, threshold })
│         │      IntersectionObserver (SSR-safe)
│         │      isIntersecting: Ref<boolean>
│         │      observe(elRef) → starts watching
│         │
│         ├── State machine
│         │      idle → loading → loaded
│         │                    → error (retryCount >= maxRetries)
│         │                    → idle → loading  (retry, exponential backoff)
│         │      lazy=true  → watch(isIntersecting) → loading
│         │      lazy=false → onMounted → loading
│         │
│         └── imgAttrs: ComputedRef
│                src    = fallback URL
│                srcset = generateSrcset(src, widths)
│                sizes  = generateSizes(sizes)
│                style  = { objectFit: fit }

├──▶ useBlurhash({ blurhash, width, height })
│         onMounted → decodeBlurhash(hash, width, height)
│                   → new ImageData(pixels, width, height)
│                   → ctx.putImageData(imageData, 0, 0)
│         canvasRef: Ref<HTMLCanvasElement | null>
│         SSR: returns null ref (canvas code never runs)

├──▶ useBreakpoints(breakpoints?)
│         Merges local breakpoints prop with global plugin breakpoints
│         resolveMediaSources(sources) → sorted [{ media, src }]

├──▶ effectivePlaceholder: ComputedRef<string | undefined>
│         placeholder prop  → used as-is (LQIP base64)
│         thumbhash prop    → decodeThumbHash(hash) → PNG data URL
│         neither           → undefined (no blur-up placeholder)

├──▶ Template structure (client)
│      <span wrapper :style="{ aspectRatio, position: relative }">
│        <canvas v-if="blurhash && width && height && !isError" />
│                                              ← BlurHash canvas placeholder
│        <img aria-hidden
│             v-if="effectivePlaceholder && !isError" />
│                                              ← LQIP / ThumbHash blur-up
│        <span v-if="isError">                ← error state
│          <slot name="error"><svg .../></slot>
│        </span>
│        <picture v-if="shouldRenderImg && !isError && needsPicture">
│                                              ← format/art-direction sources
│          <source v-for media/srcset />       ← responsive art direction
│          <source type="image/avif" />
│          <source type="image/webp" />
│          <img v-bind="imgAttrs" :decoding :fetchpriority @load @error />
│        </picture>
│        <img v-if="shouldRenderImg && !isError && !needsPicture"
│             v-bind="imgAttrs" :decoding :fetchpriority @load @error />
│                                              ← simple img (no picture)
│        <span v-if="isIdle && !blurhash && !effectivePlaceholder" />
│                                              ← grey background (no placeholder)
│      </span>

└──▶ Template structure (SSR)
       <img :src :alt :width :height :decoding :fetchpriority
            :loading="lazy ? 'lazy' : 'eager'" />

vLazyImg (Directive)
│  mounted(el, binding)
│    resolveOptions(binding) → { src, placeholder, rootMargin, ... }
│    createObserver(el, options)
│      IntersectionObserver → on intersect:
│        if placeholder: el.style.backgroundImage = url(placeholder)
│        new Image()
│          onload  → el.style.backgroundImage = url(src); onLoad()
│          onerror → onError(e)
│  updated  → disconnect old observer, create new one
│  unmounted → observer.disconnect()

Utils (pure functions, zero Vue deps)
│  blurhash-decode.ts
│    decodeBlurhash(hash, width, height) → Uint8ClampedArray  ← RGBA pixels

│  thumbhash-decode.ts
│    decodeThumbHash(hash: string | Uint8Array) → string      ← PNG data URL

└── srcset.ts
    generateSrcset(src, widths) → string
    generateSizes(sizes?) → string
    buildSizes(map, breakpoints) → string
    generatePreloadLink(href, options) → string

Размер бандла и peer-зависимости

Точка входаRawGzipPeer-зависимости
vue-image-kit ESM42.1 kB13.0 kBvue ^3.0
vue-image-kit CJS31.9 kB11.3 kBvue ^3.0
vue-image-kit/cdn ESM10.8 kB2.4 kB

Измерено по реальному результату сборки (npm run build), не поддерживается вручную — CI падает, если это отклоняется от порогов в .github/workflows/ci.yml.

Поставляется как tree-shakeable ESM (vue-image-kit.js) и CommonJS (vue-image-kit.cjs). "sideEffects": false в package.json — неиспользуемые экспорты удаляются бандлером. Если вы импортируете только vLazyImg или один composable, бандлер исключит всё остальное (VImage, декодер blurhash и т. д.).

Пример tree-shaking — использование только директивы:

ts
// В бандл попадает только vLazyImg и его логика IO.
// VImage, useBlurhash, decodeBlurhash не импортированы → не бандлятся.
import { vLazyImg } from 'vue-image-kit'
app.directive('lazy-img', vLazyImg)

Лицензия

MIT