Skip to content

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:

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

ts
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):

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

Or let VImage do it via placeholder-mode="color" (see Props).

placeholder prop — equivalent when you already have the data URL:

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

bash
npm install thumbhash --save-dev

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

The manifest will include a thumbhash field for each image alongside blurhash and placeholder.

Or generate manually in 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')
// Store in DB / manifest, pass as thumbhash prop

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

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

How it works:

  1. On the server — a blank <div> with aspect-ratio: 1200/800 is rendered to reserve space
  2. On mount — decodeBlurhash(hash, width, height) is called and the pixel data is drawn to <canvas> via ImageData
  3. The canvas stays visible while the image loads; it fades out via opacity transition when the image is ready

Using the decoder directly:

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

vue
<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> with filter: blur(20px) and transform: scale(1.05) (to hide blurred edges)
  • When the full image loads, both fade with an opacity transition — 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):

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')}`
// Pass this string as the placeholder prop

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

ts
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 })
}
FunctionReturnsOptions
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.

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>