Skip to content

HazeHash

v0.1.1UI-компонентыVanilla JSVueNuxtNode.js

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

HazeHash
01 — Назначение

Когда это пригодится

Пока картинка грузится, пользователь видит пустую рамку, а страница вокруг неё прыгает. HazeHash превращает изображение в короткую строку, которую удобно держать рядом с данными, и по ней рисует размытое превью нужной формы и цвета.

Каталог из сотен карточек товара

Вместо серых прямоугольников, пока грузятся фото, API отдаёт для каждой картинки короткую строку в 38 символов, и карточка сразу показывает размытый кадр с правильными пропорциями, без скачков вёрстки.

Хеши в базе данных или CMS

Хеш считается один раз при загрузке картинки и хранится в колонке рядом с записью: 28 байт в бинарном виде или около 40 символов в текстовом. Декодирование хеша в превью происходит на клиенте, и саму картинку для этого загружать не нужно.

Страница с серверным рендерингом

Сервер отдаёт только средний цвет и пропорции, а размытый кадр рисуется уже в браузере, поэтому разметка до и после гидрации совпадает, и предупреждений о расхождении не возникает.

Папка с картинками прямо в репозитории

Хеши для всех файлов в папке собираются при сборке сайта и кэшируются по размеру и времени изменения файла. Неизменившиеся картинки не перекодируются, а для разовой задачи хватает команды в терминале.

02 — Фичи

Коротко о главном

Выше точность при том же размере

Выше точность при том же размере

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

Хеш от 16 до 48 байт

Хеш от 16 до 48 байт

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

Пропорции и прозрачность внутри хеша

Пропорции и прозрачность внутри хеша

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

Без зависимостей и без привязки к среде

Без зависимостей и без привязки к среде

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

Быстрое декодирование и малый размер

Быстрое декодирование и малый размер

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

Vue, Nuxt и командная строка

Vue, Nuxt и командная строка

Компонент для Vue 3 и Nuxt-модуль с хешами, которые считаются при сборке, плюс команда hazehash для хеширования файлов и папок, отрисовки превью и разбора строки.

03 — Быстрый пример

Как это работает

Изображение превращается в строку из 38 символов

encodeToString() берёт RGBA-пиксели и возвращает хеш не больше 28 байт. decode() превращает его обратно в маленькое размытое превью. Считайте хеш на сервере или при сборке и отправляйте клиенту только строку.

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) }

Сначала цвет и пропорции, потом превью

Чтение среднего цвета и соотношения сторон затрагивает только заголовок хеша, поэтому рамка сразу получает цвет и форму, а размытый кадр рисуется в canvas, когда выполнится скрипт.

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)