Skip to content

HazeHash

v0.1.1UI ComponentsVanilla JSVueNuxtNode.js

Compact image placeholders: a short string in place of an image that rebuilds a blurred preview with the right aspect ratio and, when needed, transparency in a fraction of a millisecond. 49% lower error than BlurHash and 20% lower than ThumbHash.

HazeHash
Get started →
npm install hazehash
01 — Purpose

When you'd reach for this

While an image loads, the visitor sees an empty frame and the page around it jumps. HazeHash turns an image into a short string that is easy to keep next to your data, and draws a blurred preview of the right shape and colour from it.

A catalogue of hundreds of product cards

Instead of grey rectangles while the photos load, the API returns a short 38-character string for every image, and each card shows a blurred frame of the right proportions at once, with no layout jump.

Hashes in a database or a CMS

The hash is computed once when the image is uploaded and stored in a column next to the record: 28 bytes as binary, about 40 characters as text. Decoding the hash into a preview happens on the client, and the image itself does not have to be downloaded for it.

A server-rendered page

The server sends only the average colour and the aspect ratio, and the blurred frame is drawn in the browser, so the markup before and after hydration is identical and there is no mismatch warning.

A folder of images in the repository

Hashes for every file in a folder are collected during the site build and cached by file size and modification time. Unchanged images are not encoded again, and a one-off job needs only a terminal command.

02 — Features

At a glance

Lower error at the same size

Lower error at the same size

At 28 bytes the mean perceptual error is 49% lower than BlurHash and 20% lower than ThumbHash, measured on a set of 513 images: photos, screenshots, graphics and images with transparency.

A hash of 16 to 48 bytes

A hash of 16 to 48 bytes

The size is set by a budget: 16 bytes (22 characters) for the most compact hash, 28 (38 characters) by default, 36 and more for detail. The budget is a ceiling, and simple images come out smaller.

Aspect ratio and transparency inside the hash

Aspect ratio and transparency inside the hash

The string already carries the aspect ratio and, when it is needed, the alpha channel, so the space for the image is reserved without extra fields in your data.

No dependencies, not tied to a runtime

No dependencies, not tied to a runtime

The core never touches the DOM, window or Buffer, and runs in browsers, Node.js 20+ and Cloudflare Workers. The same input and options always give the same bytes, and the version 1 format will not change: a hash stored today decodes the same way in every future release.

Fast decoding, small size

Fast decoding, small size

A preview is rebuilt in about 0.3 ms. The decoder is about 2.6 KB gzip and the encoder about 6.3 KB, and a browser downloads only the part you import.

Vue, Nuxt and a command line

Vue, Nuxt and a command line

A Vue 3 component and a Nuxt module with build-time hashes, plus a hazehash command that hashes files and folders, draws previews and explains a string.

03 — Quick example

See how it works

An image becomes a 38-character string

encodeToString() takes RGBA pixels and returns a hash of at most 28 bytes. decode() turns it back into a small blurred preview. Encode on the server or at build time, and ship only the string.

encode.ts
import { encodeToString } from 'hazehash/encode'
import { decode } from 'hazehash'

// rgba: straight sRGB RGBA pixels, for example an ImageData from a canvas
const hash = encodeToString({ data: rgba, width, height })
// "Ed7UwRWKKv5znm6a7sC1tziHDNpMuCikxrYpIg": 28 bytes, 38 characters

const preview = decode(hash)
// { width: 32, height: 25, data: Uint8ClampedArray(3200) }

Colour and ratio first, the preview after

Reading the average colour and the aspect ratio only touches the header of the hash, so the box gets its colour and shape at once, and the blurred frame is drawn into a canvas when the script runs.

placeholder.ts
import { drawToCanvas } from 'hazehash/canvas'
import { getAspectRatio, getAverageColor } from 'hazehash'

const frame = document.querySelector('.frame') as HTMLElement
const { r, g, b } = getAverageColor(hash)

frame.style.backgroundColor = `rgb(${r}, ${g}, ${b})`
frame.style.aspectRatio = String(getAspectRatio(hash))

drawToCanvas(hash, document.querySelector('canvas') as HTMLCanvasElement)