PlaceholderImage Component
<PlaceholderImage> from hazehash-vue shows an image over its HazeHash placeholder: it reserves the space with the right aspect ratio, draws the blurred preview into a canvas, and fades the real <img> in over it when that loads.
<script setup lang="ts">
import { PlaceholderImage } from 'hazehash-vue'
</script>
<template>
<PlaceholderImage
hash="Ed7UwRWKKv5znm6a7sC1tziHDNpMuCikxrYpIg"
src="/photo.jpg"
alt="Mountain lake"
:width="1280"
:height="959"
/>
</template>The component only draws hashes. Create them ahead of time with hazehash encode or the Node.js helpers, or let the Nuxt module generate them during the build.
How it renders
- On the server — a
<div>withposition: relative,overflow: hidden, the average colour of the hash asbackground-colorand the aspect ratio asaspect-ratio, with the real<img>inside. There is no canvas, so the server markup and the first client render are identical and hydration cannot mismatch. - After mount — a
<canvas aria-hidden="true">with the blurred preview is added and fills the box, and the real image fades in over it once itsloadevent fires. An image that was already cached before hydration is detected throughimg.complete, so it appears at once instead of waiting for an event that has already happened. - Aspect ratio — when
widthandheightare both set they define the ratio, and otherwise it comes from the hash. With a ratio the image fills the box withobject-fit: cover; without one it is shown at its natural proportions. - Reduced motion — with
prefers-reduced-motion: reducethe fade is switched off. - An invalid hash — it never throws. The box keeps a flat background and the console gets one
[hazehash-vue] invalid placeholder hash:warning for the whole page.
Props
hash
string · default: undefined
The HazeHash of the image, as a base64url string. Without it the component renders the <img> alone. In Nuxt, when hash is not given, the module looks it up by src.
src
string · default: undefined
The URL of the real image. Without it only the placeholder is rendered.
alt
string · default: ''
The alt text of the image. The canvas is hidden from assistive technology, so the real image is the only thing it announces; leave alt empty for a decorative picture.
width
number | string · default: undefined
The intrinsic width of the image. Together with height it defines the aspect ratio of the box and is passed to the <img>.
height
number | string · default: undefined
The intrinsic height of the image.
size
number · default: 32
The long side, in pixels, of the decoded preview, limited to 4–128 like the size option of decode().
fade
number · default: 300
The duration of the fade-in of the real image, in milliseconds. 0 shows the image without a transition.
Emits
None. The <img> handles its own load event internally.
Slots
None.
Example — hashes from your data:
<script setup lang="ts">
import { PlaceholderImage } from 'hazehash-vue'
defineProps<{
photos: { url: string; hash: string; width: number; height: number; title: string }[]
}>()
</script>
<template>
<PlaceholderImage
v-for="photo in photos"
:key="photo.url"
:src="photo.url"
:hash="photo.hash"
:width="photo.width"
:height="photo.height"
:alt="photo.title"
/>
</template>Styling
The root <div> is a normal block, so it takes any attribute you give it, including class and style. Put the sizing on it: a width, a max-width or a grid cell. The height comes from the aspect ratio. Rounded corners work because the root clips its content:
<PlaceholderImage class="avatar" :hash="hash" :src="url" :width="64" :height="64" alt="" />.avatar {
width: 64px;
border-radius: 50%;
}Requirements
hazehash-vue needs Vue 3.3 or newer and hazehash as peer dependencies; see Installation.