Компактные плейсхолдеры изображений: короткая строка вместо картинки, из которой за доли миллисекунды восстанавливается размытое превью с правильными пропорциями и, если нужно, с прозрачностью. Ошибка на 49% ниже, чем у BlurHash, и на 20% ниже, чем у ThumbHash.

Пока картинка грузится, пользователь видит пустую рамку, а страница вокруг неё прыгает. HazeHash превращает изображение в короткую строку, которую удобно держать рядом с данными, и по ней рисует размытое превью нужной формы и цвета.
Вместо серых прямоугольников, пока грузятся фото, API отдаёт для каждой картинки короткую строку в 38 символов, и карточка сразу показывает размытый кадр с правильными пропорциями, без скачков вёрстки.
Хеш считается один раз при загрузке картинки и хранится в колонке рядом с записью: 28 байт в бинарном виде или около 40 символов в текстовом. Декодирование хеша в превью происходит на клиенте, и саму картинку для этого загружать не нужно.
Сервер отдаёт только средний цвет и пропорции, а размытый кадр рисуется уже в браузере, поэтому разметка до и после гидрации совпадает, и предупреждений о расхождении не возникает.
Хеши для всех файлов в папке собираются при сборке сайта и кэшируются по размеру и времени изменения файла. Неизменившиеся картинки не перекодируются, а для разовой задачи хватает команды в терминале.

При 28 байтах средняя перцептивная ошибка на 49% ниже, чем у BlurHash, и на 20% ниже, чем у ThumbHash. Это измерено на наборе из 513 изображений: фото, скриншоты, графика и картинки с прозрачностью.

Размер задаётся бюджетом: 16 байт (22 символа) для самого компактного хеша, 28 (38 символов) по умолчанию, 36 и больше для детализации. Бюджет — это максимум, простые картинки укладываются в меньший размер.

В строке уже есть соотношение сторон и, когда оно нужно, альфа-канал, поэтому место под картинку резервируется без отдельных полей в данных.

Ядро не трогает DOM, window и Buffer, работает в браузерах, Node.js 20+ и Cloudflare Workers. Один и тот же ввод и опции всегда дают одни и те же байты, а формат версии 1 не будет меняться: хеш, сохранённый сегодня, декодируется так же и в будущих релизах.

Превью восстанавливается примерно за 0,3 мс. Декодер весит около 2,6 КБ gzip, энкодер около 6,3 КБ, и браузер скачивает только ту часть, которую вы подключили.

Компонент для Vue 3 и Nuxt-модуль с хешами, которые считаются при сборке, плюс команда hazehash для хеширования файлов и папок, отрисовки превью и разбора строки.
encodeToString() берёт RGBA-пиксели и возвращает хеш не больше 28 байт. decode() превращает его обратно в маленькое размытое превью. Считайте хеш на сервере или при сборке и отправляйте клиенту только строку.
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) }Чтение среднего цвета и соотношения сторон затрагивает только заголовок хеша, поэтому рамка сразу получает цвет и форму, а размытый кадр рисуется в canvas, когда выполнится скрипт.
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)