Несколько лет назад ходили слухи о появлении в CSS спецификации под названием Houdini. Впервые описанный как способ написания полифиллов для CSS, Houdini - это набор API, который дает разработчикам программный контроль над макетом, рисованием, анимацией и многим другим.

В этой статье мы рассмотрим CSS Paint API, спецификацию Houdini, которая позволяет вам определять новое поведение рисования. Мы также быстро рассмотрим новую типизированную объектную модель CSS (или типизированную CSS-модель), также являющуюся частью Houdini, которую можно использовать с Custom Properties с большим эффектом.

Прежде чем мы перейдем к этому, я должен упомянуть поддержку браузера. На момент написания Chrome - единственный браузер, поддерживающий эти технологии, однако есть полифилы для CSS Paint API и CSS Typed OM. Ура! Итак, что же они такие?

CSS Paint API

CSS Paint API - это новый захватывающий набор, который дает вам возможность использовать CanvasRenderingContext2D там, где вы обычно используете изображение в CSS. По сути, он превращает background-image любого элемента в <canvas>.

Исходный пост Google Developers Houdini от 2016 года немного не синхронизирован со спецификацией, например, API переместились под глобальный объект window.CSS, но основные концепции остались прежними. Как и другие API-интерфейсы Houdini, Paint API основан на концепции рабочихлетов.

Worklets - это небольшие скрипты, похожие на Workers, но с некоторыми отличиями. Если рабочие процессы долговечны и предназначены для выполнения дорогостоящей в вычислительном отношении работы вне основного потока, рабочиелеты целенаправленно легковесны для выполнения более мелких операций несколько раз за кадр, в отличие от шейдеров.

Вот очень простой пример, в котором мы используем программу рисования, чтобы закрасить фон элемента в синий цвет:

<!DOCTYPE html>
<style>
  .hello-world {
    background-image: paint(hello-world);
    height: 200px;
    width: 300px;
  }
</style>
<div class="hello-world"></div>
<script>
  CSS.paintWorklet.addModule(’hello-world.js’);
</script>

И в hello-world.js (наш рабочий лист):

class HelloWorld {
  paint(context, geometry) {
    context.fillStyle = 'blue';
    context.fillRect(0, 0, geometry.width, geometry.height);
  }
}

registerPaint('hello-world', HelloWorld);

Обратите внимание, что мы определяем класс с помощью одного метода paint(). Первый аргумент, переданный ему, представляет собой двумерный контекст, подобный холсту, а второй дает геометрию окрашиваемого элемента. Метод paint() будет запускаться каждый раз при изменении геометрии, чаще всего при изменении размера.

Давайте сделаем это немного интереснее, добавив некоторую поддержку тем с помощью Custom Properties и CSS Typed OM.

Типизированная объектная модель CSS

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

CSS Typed OM направлен на то, чтобы исправить это, предоставляя хуки для создания и управления значениями CSS в JavaScript. Значения могут быть созданы с помощью методов в window.CSS global, например CSS.px(20) возвращает CSSUnitValue - объект со свойством unit и value, который при приведении к строке дает "20px". Модель также дает каждому элементу свойство attributeStyleMap и метод computedStyleMap(), которые принимают эти новые типы:

const div = document.createElement('div');
div.attributeStyleMap.set('margin-top', CSS.px(20));

Точно так же вы можете get такие свойства:

const property = div.attributeStyleMap.get(’margin-top’);
property.value === 20 // true
property.unit === 'px' // true

Типизированная OM - это гораздо больше, чем этот простой пример - я настоятельно рекомендую прочитать Работа с новой типизированной объектной моделью CSS на сайте Google Developers, но действительно крутая вещь, которую он дает вам, - это возможность регистрировать определения для новых свойств.

Давайте определим два новых свойства для нашего hello-world примера, используя новый метод CSS.registerProperty(), --background-color и --background-padding, которые мы зарегистрируем как типы <color> и <length> соответственно.

<!DOCTYPE html>
<style>
  .hello-world {
    --background-color: red;
    --background-padding: 1em;
    background-image: paint(hello-world);
    border: 1px solid black;
    height: 200px;
    width: 300px;
  }
</style>
<div class="hello-world"></div>
<script>
  CSS.registerProperty({
    name: '--background-color',
    syntax: '<color>',
    initialValue: 'blue',
    inherits: false,
  });

  CSS.registerProperty({
    name: '--background-padding',
    syntax: '<length>',
    initialValue: 0,
    inherits: false,
  });

  CSS.paintWorklet.addModule('hello-world.js');
</script>

Вы можете передать registerProperty() несколько параметров. Вы можете добавить initialValue, чтобы указать значение по умолчанию в случае, если свойство отсутствует (или недействительно), а логическое значение inherits сообщает синтаксическому анализатору, унаследованы ли значения от родительского элемента. Ключ syntax, однако, является наиболее сложным и используется для того, чтобы сообщить парсеру, какие типы CSS следует принимать как допустимые - полный список поддерживаемых строк синтаксиса см. В спецификации.

В нашем рабочем листе нам нужно внести некоторые изменения, чтобы Paint API знал, что мы хотим использовать наши настраиваемые свойства в качестве входных данных. Затем они становятся доступными для paint() метода в качестве третьего аргумента.

class HelloWorld {

  static get inputProperties() {
    return [
      '--background-color',
      '--background-padding',
    ];
  }

  paint(context, geometry, properties) {
    const color = properties.get('--background-color').value;
    const padding = properties.get('--background-padding').value;
    const height = geometry.height - padding * 2;
    const width = geometry.width - padding * 2;
    context.fillStyle = color;
    context.fillRect(padding, padding, width, height);
  }
}

registerPaint('hello-world', HelloWorld);

Обратите внимание, что даже несмотря на то, что в --background-padding указывается в em единицах, типизированный OM по умолчанию преобразует это в px, поэтому мы можем использовать его непосредственно в нашем контексте рисования без необходимости преобразовывать его самостоятельно. Если вы используете Chrome, вы можете увидеть окончательный результат здесь:

Живая демонстрация на GitHub.

Попробуйте поиграть со значениями --background-color и --background-padding в DevTools и обратите внимание, что изменения отражаются немедленно, как «настоящие» свойства CSS.

Заключение

Для Houdini это только начало. Помимо рисования, в разработке находятся API-интерфейсы для макета и анимации, а в API веб-аудио были включены рабочие листы.

Демонстрационные примеры в этой статье касаются только возможностей Paint API и Typed OM. Хотя пока они реализованы только в одном браузере, я рад возможности использовать эти функции для постепенного улучшения. А пока вы можете отслеживать принятие браузерами других производителей на сайте ishoudinireadyyet.com.