Placeholders
ThumbHash placeholder
ThumbHash is a modern alternative to BlurHash with alpha channel support, better visual quality on photos, and a shorter hash string. It decodes to a PNG data URL.
thumbhash prop — the simplest way:
<VImage src="/photo.png" alt="Photo with transparency" thumbhash="3OcRJYB4d3h/iIeHeEh3eIhw+j5n" />VImage decodes the hash automatically and uses it as a blur-up placeholder. No manual decoding needed.
Using the decoder directly (for custom markup or v-lazy-img):
import { decodeThumbHash } from 'vue-image-kit'
const dataUrl = decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → 'data:image/png;base64,...'Average color — the cheapest placeholder of all (decoded from the header, no pixels):
import { thumbHashToAverageRGBA, thumbHashToAverageColor } from 'vue-image-kit'
thumbHashToAverageRGBA('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → { r, g, b, a } (each channel 0–1)
thumbHashToAverageColor('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')
// → 'rgba(150, 146, 104, 1.000)' — drop straight into background-colorOr let VImage do it via placeholder-mode="color" (see Props).
placeholder prop — equivalent when you already have the data URL:
<VImage
src="/photo.png"
alt="Photo"
:placeholder="decodeThumbHash('3OcRJYB4d3h/iIeHeEh3eIhw+j5n')"
/>If both thumbhash and placeholder are provided, placeholder takes priority.
Generating ThumbHash hashes at build time:
Use the CLI with --thumbhash flag (requires thumbhash as a dev dependency):
npm install thumbhash --save-dev
npx vue-image-kit generate \
--input ./src/images \
--manifest ./src/assets/images.ts \
--thumbhashThe manifest will include a thumbhash field for each image alongside blurhash and placeholder.
Or generate manually in 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')
// Store in DB / manifest, pass as thumbhash propBlurhash placeholder
<VImage> decodes the blurhash string internally — no external package needed. The decoder is implemented from scratch following the open blurhash specification.
Pass blurhash together with width and height to enable the canvas placeholder:
<VImage
src="/photo.jpg"
alt="Landscape"
:width="1200"
:height="800"
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
/>How it works:
- On the server — a blank
<div>withaspect-ratio: 1200/800is rendered to reserve space - On mount —
decodeBlurhash(hash, width, height)is called and the pixel data is drawn to<canvas>viaImageData - The canvas stays visible while the image loads; it fades out via opacity transition when the image is ready
Using the decoder directly:
import { decodeBlurhash } from 'vue-image-kit'
const pixels = decodeBlurhash('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
// pixels: Uint8ClampedArray<ArrayBuffer> — RGBA, row-major
const canvas = document.createElement('canvas')
canvas.width = 32
canvas.height = 32
canvas.getContext('2d')!.putImageData(new ImageData(pixels, 32, 32), 0, 0)Generating blurhash strings:
The decoder is included — you still need to generate hashes on the server/build step. Use the official blurhash package at build time, or any server-side tool. Pass the resulting string to <VImage> as the blurhash prop.
LQIP — base64 preview
LQIP (Low Quality Image Placeholder) shows a tiny blurred version of the image while the full resolution loads.
<VImage src="/photo.jpg" alt="Photo" placeholder="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA..." />How it works:
- The base64 image is rendered as a separate
<img>withfilter: blur(20px)andtransform: scale(1.05)(to hide blurred edges) - When the full image loads, both fade with an
opacitytransition — the placeholder fades out, the full image fades in - The placeholder is
aria-hidden="true"— invisible to screen readers
Generating LQIP at build time (Node.js example):
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')}`
// Pass this string as the placeholder propClient-side encoding (user-generated content)
When a user uploads a photo, encode a placeholder in the browser so you can show a blur-up preview instantly — before the full image is uploaded or processed. Both encoders are dependency-free (the ThumbHash encoder is a faithful port of the reference, byte-identical to the thumbhash package) and accept a File/Blob, HTMLImageElement, HTMLCanvasElement, ImageBitmap, or ImageData.
import { encodeThumbHash, encodeBlurhash, decodeThumbHash } from 'vue-image-kit'
async function onFileSelected(file: File) {
const thumbhash = await encodeThumbHash(file)
// → base64 string; feed straight into <VImage :thumbhash="thumbhash">
// or decodeThumbHash(thumbhash) for a data URL preview.
const blurhash = await encodeBlurhash(file, { componentX: 4, componentY: 3 })
}| Function | Returns | Options |
|---|---|---|
encodeThumbHash(source, options?) | Promise<string> (base64) | maxSize (default/max 100) |
encodeBlurhash(source, options?) | Promise<string> | componentX (1–9, default 4), componentY (1–9, default 3), maxSize (default 64) |
The source is downscaled to maxSize on its longest edge before encoding (a ThumbHash must fit within 100×100). These require a browser/DOM — they throw in 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>