Плейсхолдеры
ThumbHash placeholder
ThumbHash — современная альтернатива BlurHash с поддержкой альфа-канала, лучшим визуальным качеством на фотографиях и более короткой строкой хэша. Декодируется в PNG data URL.
Проп thumbhash — самый простой способ:
<VImage src="/photo.png" alt="Photo with transparency" thumbhash="3OcRJYB4d3h/iIeHeEh3eIhw+j5n" />VImage автоматически декодирует хэш и использует его как blur-up плейсхолдер. Ручное декодирование не нужно.
Использование декодера напрямую (для кастомной разметки или v-lazy-img):
import { decodeThumbHash } from 'vue-image-kit'
const dataUrl = decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → 'data:image/png;base64,...'Средний цвет — самый дешёвый плейсхолдер из всех (декодируется из заголовка, без пикселей):
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 уже есть:
<VImage
src="/photo.png"
alt="Photo"
:placeholder="decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')"
/>Если заданы и thumbhash, и placeholder, приоритет у placeholder.
Генерация хэшей ThumbHash на этапе сборки:
Используйте CLI с флагом --thumbhash (требует thumbhash как dev-зависимость):
npm install thumbhash --save-dev
npx vue-image-kit generate \
--input ./src/images \
--manifest ./src/assets/images.ts \
--thumbhashМанифест будет включать поле thumbhash для каждого изображения наряду с blurhash и placeholder.
Или сгенерируйте вручную в Node.js:
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')
// Сохраните в БД / манифест, передавайте как проп thumbhashBlurhash placeholder
<VImage> декодирует строку blurhash внутренне — внешний пакет не нужен. Декодер реализован с нуля по открытой спецификации blurhash.
Передайте blurhash вместе с width и height, чтобы включить canvas-плейсхолдер:
<VImage
src="/photo.jpg"
alt="Landscape"
:width="1200"
:height="800"
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
/>Как это работает:
- На сервере — рендерится пустой
<div>сaspect-ratio: 1200/800, резервируя место - При монтировании — вызывается
decodeBlurhash(hash, width, height), и данные пикселей рисуются в<canvas>черезImageData - Canvas остаётся видимым, пока изображение загружается; исчезает через переход opacity, когда изображение готово
Использование декодера напрямую:
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) показывает крошечную размытую версию изображения, пока грузится полное разрешение.
<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):
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')}`
// Передайте эту строку как проп placeholderClient-side encoding (пользовательский контент)
Когда пользователь загружает фото, кодируйте плейсхолдер прямо в браузере, чтобы мгновенно показать blur-up превью — ещё до того, как полное изображение загружено или обработано. Оба кодировщика не имеют зависимостей (кодировщик ThumbHash — точный порт референсной реализации, байт-в-байт идентичный пакету thumbhash) и принимают File/Blob, HTMLImageElement, HTMLCanvasElement, ImageBitmap или ImageData.
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.
<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>