Houdini — зонтичное название для группы браузерных API, которые открывают разработчикам доступ к внутренностям движка CSS: к парсингу, к шагу отрисовки, к типизированным значениям свойств. Идея появилась около десяти лет назад как ответ на вечную проблему «новая CSS-фича появилась в спецификации — ждём пять лет, пока её добавят во все браузеры, либо городим JS-полифил, который мучает DOM». С Houdini полифил можно встроить ровно на тот этап рендеринга, где он нужен.
Спецификации публиковались с большим энтузиазмом примерно с 2016 по 2020 год, потом запал пропал, и часть API замерла на черновиках. В 2026-м история уже не выглядит как «будущее CSS» — это набор инструментов с очень разной судьбой: что-то стало стабильной частью платформы, что-то осталось экспериментом за флагом. В статье пройдёмся по живым API, покажем минимальные рабочие примеры и честно отметим, что застряло.
Worklet — общий примитив Houdini
Почти все API Houdini опираются на понятие worklet — облегчённой версии Web Worker. Worklet тоже выполняет JS отдельно от основного потока, но у него урезан API: нет доступа к window, document, DOM или сетевым запросам. Это сделано намеренно — задача worklet'а быть подгружаемой функцией, которую браузер вызывает в нужный момент рендеринга, а не самостоятельным агентом.
Каждая фича Houdini, расширяющая рендер, имеет свой тип worklet'а: paintWorklet, layoutWorklet, animationWorklet. Подключение во всех случаях единообразное: вызываем addModule() и передаём URL внешнего JS-файла, в котором worklet регистрируется.
CSS.paintWorklet.addModule('/worklets/checker.js');
Метод возвращает Promise, поэтому при необходимости можно дождаться загрузки перед тем, как навешивать классы. Worklet регистрируется один раз на страницу, и потом его имя доступно во всех CSS-стилях текущего документа.
Что из Houdini уже стабильно
К 2026-му году в продакшене безопасно использовать три вещи: правило @property для типизированных переменных, Paint API для генерируемых фонов и Typed Object Model для работы с CSS-значениями из JavaScript. Разберём каждую по очереди.
Правило @property и регистрация свойств
Обычные CSS-переменные, объявленные через --name: value, для браузера всегда строка. Из-за этого их нельзя анимировать через transition или @keyframes: браузер не знает, что лежит между red и blue или между 0deg и 360deg. Houdini добавляет регистрацию свойства с явным типом — и анимация начинает работать.
@property --hue {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.card {
--hue: 0deg;
background: conic-gradient(from var(--hue), #ff6b6b, #4ecdc4, #ff6b6b);
transition: --hue 1.2s linear;
}
.card:hover {
--hue: 360deg;
}
То же самое можно зарегистрировать из JavaScript:
CSS.registerProperty({
name: '--hue',
syntax: '<angle>',
inherits: false,
initialValue: '0deg',
});
В syntax разрешены <color>, <length>, <number>, <percentage>, <angle>, <url>, <image> и несколько других вариантов, включая объединение через |. Флаг inherits управляет наследованием, initial-value обязателен, если синтаксис не *.
Главное практическое применение — анимация градиентов. Без @property переход между двумя linear-gradient или conic-gradient происходит скачком, потому что браузер интерполирует строки целиком. Регистрируя угол или цвет как типизированную переменную, мы заставляем браузер интерполировать число — и градиент плавно поворачивается или меняет оттенок.
Подробнее про обычные CSS-переменные и их область видимости — в отдельной статье; @property — это их типизированная надстройка, а не замена.
Paint API — свои фоны через canvas
Paint API позволяет нарисовать значение background, border-image или mask-image программно — с помощью знакомого API CanvasRenderingContext2D. Браузер вызывает функцию paint() worklet'а каждый раз, когда нужно перерисовать элемент: при изменении размера, при смене значения CSS-переменной, при движении в трансформации.
Минимальный worklet — нарисуем шахматку:
// /worklets/checker.js
class CheckerPainter {
static get inputProperties() {
return ['--checker-size', '--checker-color'];
}
paint(ctx, geom, props) {
const size = parseInt(props.get('--checker-size').toString(), 10) || 16;
const color = props.get('--checker-color').toString().trim() || '#0d1b2a';
ctx.fillStyle = color;
for (let y = 0; y < geom.height; y += size) {
for (let x = 0; x < geom.width; x += size) {
if (((x / size) + (y / size)) % 2 === 0) {
ctx.fillRect(x, y, size, size);
}
}
}
}
}
registerPaint('checker', CheckerPainter);
Подключаем worklet и используем как фон:
<script>
CSS.paintWorklet.addModule('/worklets/checker.js');
</script>
.tile {
--checker-size: 20;
--checker-color: #0d1b2a;
background: paint(checker);
}
Статический список inputProperties декларирует, какие CSS-переменные worklet хочет читать. Если зарегистрировать --checker-size через @property как <number>, его можно даже анимировать — и paint вызовется на каждом кадре анимации.
Параметр geom в paint() — это объект с width и height элемента; ничего про положение в документе или соседей worklet знать не может. props — типизированная карта значений; props.get(...) вернёт CSSStyleValue, который для простоты приводим к строке или числу.
Что Paint API даёт по сравнению с фоновыми SVG или background-image: url(data:image/svg+xml;...):
- фон пересчитывается под актуальный размер элемента — не растягивается и не пикселизуется;
- параметры передаются обычными CSS-переменными, никаких inline-стилей в SVG;
- код фона остаётся в одном месте, рядом со стилями, а не размазан между шаблоном и стилями.
Поскольку Safari и Firefox остаются за бортом, Paint API применяют там, где эффект декоративный и его отсутствие не ломает интерфейс. Для фолбека пишем обычный background до paint(...) — браузер без поддержки просто проигнорирует непонятное значение и оставит предыдущее объявление. Если хочется явной фича-детекции, поможет директива @supports.
.tile {
background: #0d1b2a;
}
@supports (background: paint(id)) {
.tile {
background: paint(checker);
}
}
Typed Object Model — CSS-значения как объекты
До Houdini единственным способом достать вычисленный стиль из JavaScript был getComputedStyle(), возвращающий строки: "16px", "rgb(13, 27, 42)", "calc(100% - 24px)". Чтобы сделать с этим арифметику, приходилось парсить регулярками и резать px с конца.
Typed OM возвращает типизированные объекты — CSSUnitValue, CSSKeywordValue, CSSStyleValue — с явным значением и единицей измерения.
const box = document.querySelector('.box');
const map = box.computedStyleMap();
const fontSize = map.get('font-size');
console.log(fontSize.value, fontSize.unit); // 16, "px"
box.attributeStyleMap.set('padding-left', CSS.px(24));
box.attributeStyleMap.set('opacity', 0.6);
Удобные фабрики CSS.px(), CSS.em(), CSS.rem(), CSS.deg() и компания возвращают готовые CSSUnitValue, которые можно складывать и сравнивать. На практике Typed OM пригождается в библиотеках, которые работают с CSS программно: визуальные редакторы, дизайн-инструменты, утилиты для миграции стилей. В обычной разработке хватает обычных строк.
Что застряло
Остальные API из набора Houdini в продакшен не пришли. Стоит знать, что они существуют, чтобы не закладываться на них в архитектуре.
- Layout API — идея была дать разработчикам писать свои значения display: собственный masonry, выравнивание по диагонали, что угодно. Реализация была доступна за флагом в Chromium, но спецификация уже несколько лет не двигается. Параллельно браузеры сами завезли display: masonry на уровне CSS — и главный кейс закрылся без Houdini.
- Animation Worklet — обещание было запускать анимации в отдельном потоке и привязывать их к скроллу/жестам. Частично реализовано в Chromium, но получило перекрытие со стандартными scroll-driven animations через animation-timeline: scroll(...), у которых поддержка шире. В итоге animation worklet используется только в очень узких случаях, где нужен полностью кастомный таймлайн.
- Parser API — задумывался как точка расширения для собственного синтаксиса CSS. Реализаций нет ни в одном браузере, спецификация в подвешенном состоянии.
- Font Metrics API — должен был давать измерения текста (высота строки, baseline, advance каждого глифа). Стабильной реализации нет; частично задачу решает Canvas-метод measureText().
Общий итог: из «большой шестёрки» Houdini-API в реальный код доехали ровно те три, что разобраны выше. Остальное либо обогнали стандартные CSS-фичи, либо инициатива заглохла.
Когда Houdini уместен
Сценарии, где он реально окупается:
- анимация цвета или угла в градиенте — ровно тот случай, ради которого стоит зарегистрировать переменную через @property;
- декоративные генерируемые фоны — шум, шахматки, конфетти, дашед-бордеры произвольной формы. Альтернатива в виде SVG-фона плохо тянется под размер и теряет параметризацию;
- визуализации, реагирующие на CSS-переменные — например, индикатор прогресса, перерисовывающийся через paint() при изменении --progress;
- работа с CSS из инструментов — редакторы, дев-плагины, расширения, где Typed OM избавляет от парсинга строк.
Когда Houdini не нужен:
- обычные анимации интерфейса — CSS-анимации и переходы справляются и работают везде;
- masonry-сетки — используем display: masonry или grid c grid-auto-flow: dense, а не свой Layout worklet;
- скролл-анимации — стандартные scroll-driven animations через animation-timeline покрывают почти все кейсы, ради которых задумывался animation worklet.
Подводные камни
Несколько вещей, на которых легко спотыкнуться, если применять Houdini первый раз.
- CSP. paintWorklet.addModule('/worklets/foo.js') подгружает внешний JS — если на странице стоит строгий Content Security Policy с script-src 'self', отдельно для worklet'ов нужно либо разрешить тот же домен, либо использовать worker-src. Без этого worklet тихо не подгрузится, а фон останется неопределённым.
- Нет полноценной отладки. Внутри paint worklet'а не работают точки останова в DevTools, потому что код исполняется в отдельном изолированном контексте. Остаётся console.log() и инспекция вычисленных стилей у элемента.
- Worklet не видит DOM. Никакого доступа к соседним элементам, размерам родителя, состоянию скролла напрямую нет. Всё, что paint должен учитывать, передаётся через CSS-переменные.
- Без фолбека неприемлемо. Safari и Firefox не поддерживают Paint API, доля их пользователей значимая. Любое объявление background: paint(...) должно идти после обычного цвета или градиента, либо оборачиваться в @supports.
Итог
В 2026-м Houdini — уже не «будущее CSS», а небольшой набор практичных инструментов, который имеет смысл держать в виду. Берите @property — он работает везде и решает реальную задачу с анимацией градиентов. Применяйте Paint API там, где декоративный эффект допускает фолбек, и не забывайте про @supports. Typed OM держите в кармане для инструментальных задач. Остальное — Layout, Animation Worklet, Parser, Font Metrics — пока остаётся курьёзом из истории веб-платформы; не закладывайте на эти API ничего важного.
Комментарии (0)