Skip to content

Плейсхолдеры

ThumbHash placeholder

ThumbHash — современная альтернатива BlurHash с поддержкой альфа-канала, лучшим визуальным качеством на фотографиях и более короткой строкой хэша. Декодируется в PNG data URL.

Проп thumbhash — самый простой способ:

vue
<VImage src="/photo.png" alt="Photo with transparency" thumbhash="3OcRJYB4d3h/iIeHeEh3eIhw+j5n" />

VImage автоматически декодирует хэш и использует его как blur-up плейсхолдер. Ручное декодирование не нужно.

Использование декодера напрямую (для кастомной разметки или v-lazy-img):

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

const dataUrl = decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → 'data:image/png;base64,...'

Средний цвет — самый дешёвый плейсхолдер из всех (декодируется из заголовка, без пикселей):

ts
import { thumbHashToAverageRGBA, thumbHashToAverageColor } from 'vue-image-kit'

thumbHashToAverageRGBA('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → { r, g, b, a }  (каждый канал 0–1)

thumbHashToAverageColor('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → 'rgba(150, 146, 104, 1.000)'  — вставляйте прямо в background-color

Или доверьте это VImage через placeholder-mode="color" (см. Пропы).

Проп placeholder — эквивалент, когда data URL уже есть:

vue
<VImage
  src="/photo.png"
  alt="Photo"
  :placeholder="decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')"
/>

Если заданы и thumbhash, и placeholder, приоритет у placeholder.

Генерация хэшей ThumbHash на этапе сборки:

Используйте CLI с флагом --thumbhash (требует thumbhash как dev-зависимость):

bash
npm install thumbhash --save-dev

npx vue-image-kit generate \
  --input ./src/images \
  --manifest ./src/assets/images.ts \
  --thumbhash

Манифест будет включать поле thumbhash для каждого изображения наряду с blurhash и placeholder.

Или сгенерируйте вручную в Node.js:

ts
import { rgbaToThumbHash } from 'thumbhash'
import sharp from 'sharp'

const { data, info } = await sharp('photo.jpg')
  .resize(100, 100, { fit: 'inside' })
  .ensureAlpha()
  .raw()
  .toBuffer({ resolveWithObject: true })

const hash = rgbaToThumbHash(info.width, info.height, new Uint8Array(data.buffer))
const hashBase64 = Buffer.from(hash).toString('base64')
// Сохраните в БД / манифест, передавайте как проп thumbhash

Blurhash placeholder

<VImage> декодирует строку blurhash внутренне — внешний пакет не нужен. Декодер реализован с нуля по открытой спецификации blurhash.

Передайте blurhash вместе с width и height, чтобы включить canvas-плейсхолдер:

vue
<VImage
  src="/photo.jpg"
  alt="Landscape"
  :width="1200"
  :height="800"
  blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
/>

Как это работает:

  1. На сервере — рендерится пустой <div> с aspect-ratio: 1200/800, резервируя место
  2. При монтировании — вызывается decodeBlurhash(hash, width, height), и данные пикселей рисуются в <canvas> через ImageData
  3. Canvas остаётся видимым, пока изображение загружается; исчезает через переход opacity, когда изображение готово

Использование декодера напрямую:

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

const pixels = decodeBlurhash('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
// pixels: Uint8ClampedArray<ArrayBuffer> — RGBA, по строкам

const canvas = document.createElement('canvas')
canvas.width = 32
canvas.height = 32
canvas.getContext('2d')!.putImageData(new ImageData(pixels, 32, 32), 0, 0)

Генерация строк blurhash:

Декодер включён — хэши всё равно нужно генерировать на сервере/этапе сборки. Используйте официальный пакет blurhash на этапе сборки или любой серверный инструмент. Передайте получившуюся строку в <VImage> как проп blurhash.

LQIP — base64 preview

LQIP (Low Quality Image Placeholder) показывает крошечную размытую версию изображения, пока грузится полное разрешение.

vue
<VImage src="/photo.jpg" alt="Photo" placeholder="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA..." />

Как это работает:

  • Base64-изображение рендерится отдельным <img> с filter: blur(20px) и transform: scale(1.05) (чтобы скрыть размытые края)
  • Когда полное изображение загружается, оба переходят через opacity: плейсхолдер исчезает, полное изображение проявляется
  • Плейсхолдер имеет aria-hidden="true" — невидим для скринридеров

Генерация LQIP на этапе сборки (пример на Node.js):

ts
import sharp from 'sharp'

const buffer = await sharp('photo.jpg').resize(20).jpeg({ quality: 20 }).toBuffer()

const lqip = `data:image/jpeg;base64,${buffer.toString('base64')}`
// Передайте эту строку как проп placeholder

Client-side encoding (пользовательский контент)

Когда пользователь загружает фото, кодируйте плейсхолдер прямо в браузере, чтобы мгновенно показать blur-up превью — ещё до того, как полное изображение загружено или обработано. Оба кодировщика не имеют зависимостей (кодировщик ThumbHash — точный порт референсной реализации, байт-в-байт идентичный пакету thumbhash) и принимают File/Blob, HTMLImageElement, HTMLCanvasElement, ImageBitmap или ImageData.

ts
import { encodeThumbHash, encodeBlurhash, decodeThumbHash } from 'vue-image-kit'

async function onFileSelected(file: File) {
  const thumbhash = await encodeThumbHash(file)
  // → base64-строка; передавайте прямо в <VImage :thumbhash="thumbhash">
  //   или decodeThumbHash(thumbhash) для превью в виде data URL.

  const blurhash = await encodeBlurhash(file, { componentX: 4, componentY: 3 })
}
ФункцияВозвращаетОпции
encodeThumbHash(source, options?)Promise<string> (base64)maxSize (по умолчанию/макс. 100)
encodeBlurhash(source, options?)Promise<string>componentX (1–9, по умолчанию 4), componentY (1–9, по умолчанию 3), maxSize (по умолчанию 64)

Источник уменьшается до maxSize по длинной стороне перед кодированием (ThumbHash должен помещаться в 100×100). Требуют браузер/DOM — выбрасывают исключение в SSR.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { encodeThumbHash } from 'vue-image-kit'

const hash = ref('')
async function handleUpload(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (file) hash.value = await encodeThumbHash(file)
}
</script>

<template>
  <input type="file" accept="image/*" @change="handleUpload" />
  <VImage v-if="hash" :src="previewUrl" alt="Preview" :thumbhash="hash" />
</template>