От автора: в статье будут рассмотрены низкоуровневые API, которые подключаются к процессу стилей и макета движка рендеринга вашего браузера.
Примечание. Исходный код демонстрации, обсуждаемой во второй половине этой статьи, можно найти на GitLab.
Как я уже упоминал в своей недавней статье об анимации холста внутри компонентов React, мне нравится HTML-холст. Поэтому я был очень взволнован, узнав о новых API-интерфейсах CSS Houdini, когда читал короткую статью Стивена Фулхэма об этом на css-tricks.com.
Основная причина моего восхищения заключается в том, что API рисования позволяет создавать нативные изображения CSS путем рисования в PaintRenderingContext2D (который в значительной степени является точной копией 2D-контекста, в котором мы рисуем, когда используем обычный API Canvas, за исключением небольшого набора функций).
Используя API рисования, мы можем программно рисовать изображения и использовать их в CSS. При рисовании мы можем получать параметры с информацией, поступающей из DOM и применяемых таблиц стилей.
Веб-документация MDN описывают это так: Houdini — это набор низкоуровневых API-интерфейсов, которые раскрывают части движка CSS, давая разработчикам возможность расширять CSS, подключаясь к процессу стилизации и компоновки движка рендеринга браузера.
Я нахожу это очень захватывающим, особенно потому, что мы видим признаки того, что все основные браузеры реализуют это.
В этой статье мы рассмотрим CSS Painting API (и Worklets).
Важное примечание: CSS Houdini в целом все еще является экспериментальной технологией. Но, как упоминалось ранее, большинство браузеров реализуют его или серьезно рассматривают возможность его реализации. Google Chrome — поддерживает Painting API начиная с версии 65, так что именно этот браузер мы будем использовать сегодня.
CSS Painting API
Используя этот API, мы можем программно рисовать изображения и использовать эти изображения в CSS. Вот что мы создадим:
Как ни странно, это три элемента DIV и только три элемента DIV. Об этом свидетельствует файл index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>CSS Painting API example</title> <script src="main.js"></script> <link rel="stylesheet" href="style.css" type="text/css"> </head> <body> <div class="pane"> Lorem ipsum... </div> <div class="pane large"> Lorem ipsum... </div> <div class="pane mini"> Lorem ipsum... </div> </body> </html>
«Стилизация» (рисование…) фона трех панелей выполняется с помощью API рисования программно. Все три панели отрисовываются одной и той же функцией. Давайте посмотрим на класс CSS pane в таблице стилей:
.pane { background-image: paint(pane); font-size: 1em; --dot-spacing: 5px; } .pane.mini { font-size: 12px; --pane-color: #7470b5; } .pane.large { font-size: 24px; --pane-color: #b068bd; }
Мы рисуем что-то, называемое панелью, в строке 2. Это требует объяснения, и мы скоро его рассмотрим.
Важно отметить, что функция рисования не выполняется только один раз. Она выполняется — и перерисовывает изображение — всякий раз, когда движок рендеринга браузера дает ей инструкции сделать это. Примеры этого: когда пользователь изменяет размер окна браузера или когда другие свойства CSS элемента DIV изменяются, и элемент DIV получает другое соотношение сторон.
Поскольку наша функция будет «подключена» к механизму рендеринга, это очень производительная и незатратная операция.
Помимо этого, стоит упомянуть, что все панели имеют разный размер шрифта (имейте это в виду, мы еще вернемся к этому позже). И мы видим, что используются две пользовательские переменные CSS: ‑‑dot‑spacing и ‑‑pane‑color.
В переменных CSS нет ничего нового. Они существуют с 2014/2016 (соответственно в Firefox / Google Chrome). Но один из новых API-интерфейсов CSS Houdini, называемый API свойств и значений CSS, позволяет нам регистрировать эти пользовательские переменные, чтобы браузер знал о них больше, что пригодится.
Внутри таблицы стилей мы можем зарегистрировать их следующим образом:
@property --pane-color { syntax: "<color>"; inherits: true; initial-value: #646464; } @property --dot-spacing { syntax: "<length>"; inherits: true; initial-value: 5px; } .pane { /* etc. */ }
Обратите внимание, что мы также могли сделать это в JavaScript. Результат будет таким же.
Так зачем мы это делаем? Теперь браузер и его движок визуализации знают подробности об этих свойствах. Он знает, что pane color содержит значение цвета и что значение по умолчанию — «#646464». И dot spacing содержит значение длины, по умолчанию «5 пикселей».
Две новые переменные теперь называются зарегистрированными пользовательскими переменными. Вернемся к этой строке:
background-image: paint(pane);
API рисования позволяет рисовать изображения. Функция paint получает один параметр. Этот параметр представляет собой класс JavaScript, зарегистрированный как Paint в файле с именем worklet.js:
class Pane { // ... } registerPaint("pane", Pane);
Но! Этот код не может быть выполнен в обычной среде выполнения JavaScript. Он должен быть выполнен внутри так называемого — отсюда и название файла — Worklet:
«Интерфейс Worklet — это облегченная версия Web Workers, которая дает разработчикам доступ к низкоуровневым частям конвейера рендеринга. С помощью Worklets вы можете запускать код JavaScript и WebAssembly для рендеринга графики или обработки звука там, где требуется высокая производительность».
Мы можем убедиться, что Worklet будет выполнен, добавив следующую строку в обычный файл JavaScript (main.js), который загружается внутри index.html, например:
// main.js CSS.paintWorklet.addModule("worklet.js");
Если этот вызов возвращает ошибку, ваш браузер не поддерживает Painting API. Теперь класс Paint «Pane» зарегистрирован под именем «pane», и мы можем использовать его в CSS, как мы видели ранее:
background-image: paint(pane);
Подробная информация о панели класса Paint
Давайте рассмотрим детали класса Paint внутри worklet.js:
class Pane { static get inputProperties() { return ["font-size", "--pane-color", "--dot-spacing"]; } static get contextOptions() { return { alpha: true }; } paint(ctx, size, styleMap) { // ... } }
Статическая функция inputProperties должна возвращать список свойств CSS, которые нас интересуют, когда мы будем рисовать наше изображение. Это произвольно, и вы можете добавить любое свойство CSS, какое захотите.
Возвращаемое значение contextOptions указывает, что мы хотим использовать прозрачность в нашем холсте.
И, наконец, в строке 10 функция paint. В этой функции мы сделаем фактический рисунок. В нашем случае он получает три параметра:
ctx: 2D-контекст нашего холста. Это должно быть вам понятно, если вы знакомы с обычным элементом HTML Canvas.
size: экземпляр PaintSize с двумя свойствами: .width и .height. Это вычисленные размеры элемента HTML, для которого мы рисуем изображение. Включая отступы, если они установлены.
styleMap: доступное только для чтения представление блока (источника) объявления CSS. Это экземпляр StylePropertyMapReadOnly и содержит только значения для свойств, которые мы определили внутри статической функции inputProperties.
Для получения дополнительных сведений о последнем параметре вы можете прочитать об API типизированной объектной модели CSS и / или об интерфейсе StylePropertyMapReadOnly.
Совет: вы можете получить полный StylePropertyMapReadOnly (содержащий все вычисленные стили CSS) для любого элемента HTML в DOM, вызвав computedStyleMap в браузерах, которые его поддерживают:
const styleMap = document.getElementById('myElement').computedStyleMap();
Чтобы прояснить, как мы можем получить значения из такого экземпляра, я создал фрагмент с некоторыми встроенными комментариями:
class Pane { static get inputProperties() { return ["font-size", "--pane-color", "--dot-spacing"]; } paint(ctx, size, styleMap) { let item; // font-size is a CSS property with a syntax "<length>" value. // Therefor styleMap.get() will return a `CSSUnitValue` object. // It has two properties: .value, and .unit item = styleMap.get("font-size"); console.log(item.value); // output: 12 console.log(item.unit); // output: "px" console.log(item.toString()); // output: "12px" // --pane-color, one of our custom properties, // is a CSS property with a syntax "<color>" value. // We defined this in our stylesheet when we registered the property. // Therefor styleMap.get() will return a `CSSStyleValue` object. // It has no public properties, but we can cast it to a string. item = styleMap.get("--pane-color"); console.log(item.toString()); // output: "rgb(100, 100, 100)" } }
Рисование самого изображения
Вернемся к логике нашей маленькой демонстрации. Единственное, что осталось, — это содержимое тела функции рисования.
Мы могли бы начать с простого рисования заполненного прямоугольника в качестве фонового изображения (вы должны распознать команды рисования на обычном холсте HTML):
class Pane { // ... paint(ctx, size, styleMap) { const paneColor = String(styleMap.get("--pane-color")); ctx.fillStyle = paneColor; ctx.fillRect(0, 0, size.width, size.height); } }
… Который будет выглядеть так:
Нарисованные фоновые изображения!
Обратите внимание, как мы использовали пользовательское свойство CSS pane color для динамической установки стиля заливки.
Используя инструменты разработчика браузера, мы можем даже обновить значение цвета, и фоновое изображение будет мгновенно перекрашено!
Если мы обновим функцию рисования с помощью более причудливой логики (вы можете посмотреть подробности в файле worklet.js в репозитории), фоновое изображение будет выглядеть так:
CSS переходы
Обратите внимание на последовательность точек в правом нижнем углу. Расстояние между этими точками определяется значением пользовательского свойства CSS dot spacing, которое по умолчанию имеет значение «5 пикселей».
Давайте немного развлечемся, добавив к этому свойству переход. Мы также увеличиваем значение расстояния между точками при наведении курсора на элемент .pane:
@property --dot-spacing { syntax: "<length>"; inherits: true; initial-value: 5px; } .pane { /* ... */ --dot-spacing: 5px; transition: --dot-spacing 0.5s; } .pane:hover { --dot-spacing: 15px; }
… Что приводит к плавной анимации при наведении курсора на элементы:
Это показывает, насколько производительна функция рисования и что она может выполняться 60+ раз в секунду. Причина, по которой мы можем добавить переход к пользовательской переменной CSS, заключается в том, что мы зарегистрировали свойство ранее.
Совет: мы также могли бы добавить бесконечную анимацию CSS для одного из пользовательских свойств CSS, что привело бы к созданию настраиваемого нарисованного анимированного фона!
Заключение
Я нахожу Houdini Painting API не только интересным, но и простым для понимания и мощным (производительным). Я полон идей по использованию этой новой функции, но мне нужно набраться терпения и подождать, пока все основные браузеры не начнут поддерживать ее, прежде чем мы сможем рассмотреть возможность ее использования в производственной среде. Спасибо за внимание!
Автор: Gerard van der Put
Источник: medium.com
Редакция: Команда webformyself.