Skip to content

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.

vue
<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> with position: relative, overflow: hidden, the average colour of the hash as background-color and the aspect ratio as aspect-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 its load event fires. An image that was already cached before hydration is detected through img.complete, so it appears at once instead of waiting for an event that has already happened.
  • Aspect ratio — when width and height are both set they define the ratio, and otherwise it comes from the hash. With a ratio the image fills the box with object-fit: cover; without one it is shown at its natural proportions.
  • Reduced motion — with prefers-reduced-motion: reduce the 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:

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

vue
<PlaceholderImage class="avatar" :hash="hash" :src="url" :width="64" :height="64" alt="" />
css
.avatar {
  width: 64px;
  border-radius: 50%;
}

Requirements ​

hazehash-vue needs Vue 3.3 or newer and hazehash as peer dependencies; see Installation.