Pull to refresh

CSS Houdini: практическое руководство

Reading time13 min
Views19K
Original author: Adrian Bece
Доброго времени суток, друзья!

Что такое Houdini?


Houdini (Гудини) — коллекция API браузера, значительно улучшающих процесс веб разработки, включая разработку стандартов CSS. Разработчики смогут расширять CSS, используя JavaScript, влияя на рендеринг CSS и указывая браузеру, как следует применять стили. Это обеспечит значительное повышение производительности и стабильности, нежели использование полифилов.

Гудини состоит из двух групп API — высокоуровневые API и API низкого уровня.

API высокого уровня связаны с процессом рендеринга (стиль — макет — рисование — композиция). Эта группа включает:

  • Paint API — позволяет расширять CSS на шаге (имеется ввиду стадия рендеринга) отрисовки визуальных элементов (цвет, фон, границы и т.д.).
  • Layout API — позволяет расширять CSS на шаге определения размеров, позиции и выравнивания элементов.
  • Animation API — «точка расширения» на шаге отображения и анимирования элементов.

Низкоуровневые API являются основой для высокоуровневых и включают в себя:

  • API типизированной объектной модели (Typed Object Model)
  • API пользовательских свойств и значений (Custom Properties & Values)
  • API метрик шрифта (Font Metrics)
  • Ворклеты (Worklets)

Будущее CSS


В отличие от обычного CSS Гудини позволяет разработчикам расширять CSS более естественным способом. Означает ли это, что спецификации CSS перестанут развиваться, а новые стандарты приниматься? Отнюдь. Цель Гудини — способствовать разработке новых возможностей CSS через создание рабочих прототипов, которые легко могут быть стандартизированы.

Кроме того, разработчики смогут легко делиться открытыми CSS ворклетами, не задумываясь о совместимости.

API типизированной объектной модели (ТОМ)


До появления Гудини единственным способом взаимодействия JS и CSS было приведение CSS к строке и ее модификация. «Парсинг» и переопределение стилей вручную может быть сложным и подвержен ошибкам из-за необходимости двойного преобразования типа значения (например, из числа в строку и обратно). Также приходится вручную указывать единицы измерения нового значения.

selectedElement.style.fontSize = newFontSize + 'px' // newFontSize = 20
console.log(selectedElement.style.fontSize) // 20px

ТОМ придает свойствам CSS более семантическое значение, представляя их в виде типизированных JS объектов. Это значительно улучшает производительность, стабильность и облегчает поддержку кода. Значения CSS представлены интерфейсом CSSUnitValue, состоящим из значения и свойства «единицы измерения».

{
  value: 20,
  unit: 'px'
}

Этот интерфейс может быть использован со следующими новыми свойствами:

  • computedStyleMap(): для парсинга вычисляемых (не встроенных) стилей. Этот метод вызывается перед парсингом или применением других методов.
  • attributeStyleMap: для парсинга и модификации встроенных стилей. Это свойство элемента.

// получаем вычисляемые стили из таблицы стилей (начальное значение)
selectedElement.computedStyleMap().get('font-size') // { value: 20, unit: 'px' }

// устанавливаем встроенные стили
selectedElement.attributeStyleMap.set('font-size', CSS.em(2))
selectedElement. attributeStyleMap.set('color', 'blue')

// вычисляемые стили не изменились
selectedElement.computedStyleMap().get('font-size') // { value: 20, unit: 'px' }

// получаем новые встроенные стили
selectedElement.attributeStyleMap.get('font-size') // { value: 2, unit: 'em' }

Обратите внимание на то, как используются типы CSS при присваивании нового значения. Использование такого синтаксиса позволяет избежать связанных с типами проблем, а результирующий код становится более безопасным.

Рассматриваемый API включает не только get и set, но и другие методы, например:

  • clear: удаляет все встроенные стили
  • delete: удаляет определенное свойство CSS и его значение
  • has: возвращает true/false в зависимости от наличия указанного свойства
  • append: добавляет дополнительное значение в свойство, поддерживающее несколько значений

Обнаружение


let selectedElement = document.getElementById('example')

if(selectedElement.attributeStyleMap){
  // ...
}

if(selectedElement.computedStyleMap){
  // ...
}

Статус спецификации


Рабочий черновик: опубликован для обсуждения сообществом.

Поддержка


Chrome Edge Opera Firefox Safari
Поддерживается Поддерживается Поддерживается Не поддерживается Частичная поддержка

API пользовательских свойств и значений


Этот API позволяет разработчикам расширять переменные CSS, определяя тип, начальное значение и наследование. Для того, чтобы определить пользовательское свойство необходимо его зарегистрировать с помощью метода «registerProperty». Этот метод определяет, как браузеры должны применять свойство и обрабатывать ошибки.

CSS.registerProperty({
  name: '--colorPrimary',
  syntax: '<color>',
  inherits: false,
  initialValue: 'blue',
})

В качестве аргумента данному методу передается объект со следующими свойствами:

  • name: название пользовательского свойства
  • syntax: инструкция для парсинга. Предустановленными значениями являются: <color>, <integer>, <number>, <length>, <percentage> и др.
  • initialValue: значение по умолчанию (до переопределения, а также при возникновении ошибок)

В приведенном примере было определено пользовательское свойство с типом <color>. Это свойство будет использовано для определения градиента. Обычный CSS не поддерживает переход градиента. Обратите внимание на то, как будет использовано пользовательское свойство для определения transition.

.gradient-box {
  background: linear-gradient(45deg, rgba(255, 255, 255, 1) 0% var(--colorPrimary) 60%);
  transition: --colorPrimary 0.5s ease;
  ...
}

.gradient-box:hover {
  --colorPrimary: red;
  ...
}

Браузер не знает о том, как выполнить переход для градиента, но он знает, как сделать это для цвета. Именно поэтому мы определили тип свойства как <color>. В браузере, поддерживающем Гудини, изменение градиента произойдет при наведении курсора. Позиция градиента, измеряемая в процентах, также может быть изменена при помощи пользовательского свойства CSS (зарегистрированного как <percentage>).

Вероятно, в будущем появится возможность регистрировать пользовательское свойство прямо в CSS.

@property --colorPrimary {
  syntax: '<color>';
  inherits: false;
  initial-value: blue;
}

Пример


В этом простом примере показано, как можно изменить цвет и контрольные точки градиента с помощью пользовательских свойств CSS. Код примера можно посмотреть здесь.



Обнаружение


if(CSS.registeredProperty) {
  // ...
}

Статус спецификации


Рабочий черновик: опубликован для обсуждения сообществом.

Поддержка


Chrome Edge Opera Firefox Safari
Поддерживается Поддерживается Поддерживается Не поддерживается Не поддерживается

API метрик шрифта


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

Статус спецификации


Коллекция идей: черновик не опубликован.

Браузерами не поддерживается.

Ворклеты


Прежде чем переходить к следующим API, необходимо понять концепцию ворклетов. Ворклеты — это скрипты, запускающиеся во время рендеринга и не зависящие от основного JS кода. Они расширяют возможности движка рендеринга, спроектированы для параллельного выполнения (2 или более экземпляра), не блокируют основной поток, имеют ограниченный доступ к глобальной области видимости и вызываются движком по необходимости. Ворклеты могут выполняться только через HTTPS (в продакшне) или через localhost (для целей разработки и тестирования).

Гудини включает следующие ворклеты, расширяющие движок рендеринга браузера:

  • Paint Worklet — Paint API
  • Animation Worklet — Animation API
  • Layout Worklet — Layout API

Paint API


Paint API позволяет разработчикам использовать функции JS для рисования фона, границ или содержимого элемента, используя 2D Rendering Context, который является подмножеством HTML5 Canvas API. Paint API использует Paint Worklet для рисования изображения, которое зависит от изменений в CSS (например, изменений в переменных CSS). Те, кто хорошо знаком с Canvas API, почувствуют себя как дома, работая с Paint API.

Создание Paint Worklet состоит из нескольких этапов:

  1. Написать и зарегистрировать ворклет с помощью функции «registerPaint»
  2. Вызвать ворклет в HTML или JS с помощью CSS.paintWorklet.addModule
  3. Использовать метод paint() в CSS вместе с названием ворклета и передаваемыми аргументами

Давайте посмотрим на функцию registerPaint, которая используется для регистрации и определения функциональности Paint Worklet.

registerPaint('paintWorkletExample', class {
  static get inputProperties() { return ['--myVariable']; }
  static get inputArguments() { return ['<color>']; }
  static get contextOptions() { return {alpha: true} }

  paint(ctx, size, properties, args) {
    // ...
  }
})

Функция «registerPaint» состоит из следующих частей:

  • inputProperties: массив пользовательских свойств CSS, за которыми наблюдает ворклет. Этот массив представляет собой зависимости ворклета
  • inputArguments: массив аргументов, которые могут быть переданы из функции во внешний CSS
  • contextOptions: прозрачность цветов. Если в значении стоит false, все цвета будут полностью непрозрачными
  • paint: главная функция, принимающая следующие аргументы:

    • ctx: контекст 2D-рисования, почти идентичный 2D контексту рисования Canvas API
    • size: объект со значениями ширины и высоты элемента. Значения зависят от процесса рендеринга макета. Размер холста такой же как действительный размер элемента
    • properties: переменные, содержащиеся в inputProperties
    • args: массив аргументов, передаваемых функции «paint»

После регистрации ворклета, его необходимо вызвать в HTML, указав путь к файлу.

CSS.paintWorklet.addModule('path/to/worklet/file.js')

Ворклеты могут быть добавлены из любого внешнего источника (например, из CDN), что делает их модульными и переиспользуемыми.

CSS.paintWorklet.addModule('https://url/to/worklet/file.js')

После вызова ворклета он может быть использован в CSS с помощью функции «paint». Эта функция в качестве первого параметра принимает зарегистрированное имя ворклета и все аргументы, указанные в inputArguments. С этого момента браузер знает, когда вызывать ворклет и какие действия пользователя приводят к изменению тех или иных значений пользовательских свойств CSS.

.example-element {
  // paintWorkletExample - название ворклета
  // blue - аргумент, передаваемый ворклету
  background: paint(paintWorkletExample, blue);
}

Пример


Следующий пример демонстрирует использование Paint API, а также модульность и переиспользуемость ворклетов. В нем используется ворклет пульсации из репозитория Google Chrome Labs. Код примера смотрите здесь.



Обнаружение


if(‘paintWorklet’ in CSS){
  // …
}

@supports(background: paint(paintWorkletExample)){
  // …
}

Статус спецификации


Рекомендация: стабильный рабочий черновик, готовый к использованию.

Поддержка


Chrome Edge Opera Firefox Safari
Поддерживается Поддерживается Поддерживается Не поддерживается Не поддерживается

Animation API


Этот API расширяет веб анимации через обработку различных событий (прокрутка, наведение, клик и т.д.) и улучшает производительность, запуская анимации в собственном потоке посредством ворклета анимации.

Как и любой другой ворклет, ворклет анимации сначала должен быть зарегистрирован.

registerAnimation(‘animationWorkletExample’, class {
  constructor(options){
    // …
  }
  animate(currentTime, effect){
    // …
  }
})

Этот класс включает две функции:

  • constructor: вызывается при создании нового экземпляра. Используется для общей настройки
  • animate: основная функция, содержащая логику анимации. Принимаются следующие параметры:

    • currentTime: значения (отметки) времени на определенной шкале времени
    • effect: массив эффектов, используемых в анимации

После регистрации ворклет включается в основной файл JS, анимация (элемент, кадры, настройки) добавляется и привязывается к шкале времени. Концепция отметок на шкале времени и основы веб анимации — в следующем разделе.

// добавляем ворклет анимации
await CSS.animationWorklet.addModule(‘path/to/worklet/file.js’)

// выбираем элемент для анимации
const elementExample = document.getElementById(‘element-example’)

// определяем эффекты
const effectExample = new KeyframeEffect(
  elementExample, // анимируемый элемент
  [ // … ], // кадры анимации
  { // … } // настройки - продолжительность, задержка, количество повторов и т.д.
)

// создаем новый экземпляр ворклета анимации и запускаем его
new WorkletAnimation(
  ‘animationWorkletExample’ // название ворклета
  effectExample, // график анимации
  document.timeline, // отметки времени
  {},  // настройки для конструктора
).play()

Отметки времени


Веб анимация основана на отметках времени — контрольных точках для эффектов на шкале времени анимации. Например, разберем повторяющуюся линейную анимацию, которая состоит из трех кадров (начало, середина, конец), запускается через 1 секунду после полной загрузки страницы (задержка) и длится 4 секунды.

Отметки времени эффектов будут выглядеть так (для анимации продолжительностью 4 секунды и без задержки):

Отметки времени Кадр анимации
0ms Первый кадр — начало анимации
2000ms Второй кадр — середина анимации
4000ms Последний кадр — конец анимации или сброс к первому кадру

effect.localTime со значением 3000ms (учитывая задержку в 1000ms) привязывает анимацию к среднему кадру на шкале времени (1000ms задержка + 2000ms средний кадр). Тот же эффект будет достигнут при установке 7000ms и 11000ms, поскольку анимация повторяется каждые 4000ms.

animate(currentTime, effect){
  effect.localTime = 3000 // 1000ms задержка + 2000ms средний кадр
}

При постоянном значении effect.localTime анимация будет заблокирована на определенном кадре. Поэтому значение effect.localTime должно меняться. Таким значением должна быть функция, привязанная к currentTime или другой переменной.

Вот как выглядит код линейной анимации:

animate(currentTime, effect){
  effect.localTime = currentTime // y = x линейная функция
}

Шкала времени (document.timeline) Отметка времени Кадр
startTime + 0ms (истекшее время) startTime + 0ms Первый
startTime + 1000ms (истекшее время) startTime + 1000ms (задержка) + 0ms Первый
startTime + 3000ms (истекшее время) startTime + 1000ms (задержка) + 2000ms Средний
startTime + 5000ms (истекшее время) startTime + 1000ms (задержка) + 4000ms Последний/первый
startTime + 7000ms (истекшее время) startTime + 1000ms (задержка) + 6000ms Средний
startTime + 9000ms (истекшее время) startTime + 1000ms (задержка) + 8000ms Последний/первый

Отметки времени не ограничены значением 1:1. Animation API позволяет разработчикам манипулировать отметками посредством функции «animate», используя стандартные функции JS для создания сложных эффектов. Анимации также могут различаться на каждой итерации (при повторяемости анимации).

Анимация может быть привязана не только к загрузке документа, но также к действиям пользователя. Такое действие пользователя как прокрутка страницы может использоваться в анимации через объект «ScrollTimeline». Например, анимация может начинаться при прокрутке 200px и заканчиваться при прокрутке 800px.

const scrollTimelineExample = new ScrollTimeline({
  scrollSource: scrollElement, // элемент, за прокруткой которого мы наблюдаем
  orientation: ‘vertical’, // направление прокрутки
  startScrollOffset: ‘200px’, // начало отметки
  endScrollOffset: ‘800px’, // конец отметки
  timeRange: 1200, // продолжительность эффекта
  fill: ‘forwards’ // направление анимации
})

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

Пример


Следующий пример демонстрирует нелинейную анимацию. В нем используется функция Гаусса с одинаковым по времени вращением туда и обратно. Код примера смотрите здесь.



Обнаружение


if(CSS.animationWorklet){
  // …
}

Статус спецификации


Первый публичный рабочий черновик: готов для обсуждения сообществом, может быть изменен в будущем.

Поддержка


Chrome Edge Opera Firefox Safari
Частичная поддержка Частичная поддержка Частичная поддержка Не поддерживается Не поддерживается

Layout API


Layout API позволяет разработчикам расширять процесс рендеринга макета, определяя новые модули для использования в CSS свойстве “display”. Этот API представляет новые концепции, является очень сложным и предлагает большое количество настроек для разработки пользовательских алгоритмов работы с макетом страницы.

Перво-наперво ворклет необходимо зарегистрировать.

registerLayout(‘exampleLayout’, class{
  static get inputProperties() { return [‘--example-variable’] }

  static get childrenInputProperties() { return [‘--exampleChildVariable’] }

  static get layoutOptions(){
    return {
      childDisplay: ‘normal’,
      sizing: ‘block-like’
    }
  }

  intrinsicSizes(children, edges, styleMap){
    // …
  }

  layout(children, edges, constraints, styleMap, breakToken){
    // …
  }
})

Регистрация ворклета включает следующие методы:

  • inputProperties: массив пользовательских свойств CSS, за которыми наблюдает ворклет и которые принадлежат родителю — элементу, вызвавшему рендеринг макета. Этот массив представляет собой зависимости ворклета макета
  • childrenInputProperties: массив пользовательских свойств CSS, за которыми наблюдает ворклет и которые принадлежат потомку
  • layoutOptions: определяет следующие свойства макета:

    • childDisplay: предустановленными значениями являются block и normal. Определяет способ отображения элемента (блочный или строчный)
    • sizing: предустановленными значениями являются block-like и manual. Определяет необходимость предварительного вычисления размеров элемента (если не указаны)
  • intrinsicSizes: определяет, как контейнер или его содержимое отображается в контексте макета:

    • children: потомок элемента, вызвавшего рендеринг макета страницы
    • edges: границы контейнера
    • styleMap: типизированная объектная модель стилей контейнера
  • layout: основная функция для работы с макетом:

    • children: дочерний элемент
    • edges: границы
    • constraints: ограничения, налагаемые родительским макетом
    • styleMap: типизированная объектная модель стилей контейнера
    • breakToken: контрольная точка для разделения макета при пагинации или печати

Далее ворклет добавляется в HTML или JS файл.

CSS.layoutWorklet.addModule(‘path/to/worklet/file.js’)

Делаем ссылку на ворклет в файле со стилями.

.example-element {
  display: layout(exampleLayout)
}

Как Layout API работает с макетом


В предыдущем примере мы определили exampleLayout.

.example-element называется родительским макетом, включающим внутренние отступы, границы и ползунки прокрутки. Родительский макет состоит из дочерних элементов, которые называются текущими макетами. Текущие макеты являются целевыми элементами, макеты которых «кастомизируются» с помощью Layout API. Например, при использовании «display: flex» потомки элемента перестраиваются согласно гибкому макету. Это похоже на работу Layout API.

Каждый текущий макет состоит из дочерних макетов, содержащих алгоритмы рендеринга макета дочернего элемента — LayoutChild (включая псевдоклассы ::before и ::after). LayoutChild — генерируемый средствами CSS контейнер, содержащий данные о стилях (без данных о макете). Элементы LayoutChild автоматически создаются браузером на стадии применения стилей. Дочерний макет может создавать Fragment, содержащий инструкции по рендерингу макета.

Пример


В этом примере также используется репозиторий Google Chrome Labs, однако текст заменен изображениями. Код примера смотрите здесь .



Обнаружение


if(CSS.layoutWorklet){
  // …
}

Статус спецификации


Первый публичный рабочий черновик: готов для обсуждения сообществом, может быть изменен в будущем.

Поддержка


Chrome Edge Opera Firefox Safari
Частичная поддержка Частичная поддержка Частичная поддержка Не поддерживается Не поддерживается

Гудини и прогрессивное улучшение


Несмотря на то, что Гудини на сегодняшний день не имеет оптимальной поддержки браузеров, он может использоваться для прогрессивного улучшения. Если вы не знакомы с концепцией прогрессивного улучшения, советую взглянуть на эту статью. При использовании Гудини необходимо учитывать следующее:

Всегда определяйте поддержку во избежание ошибок


Каждый API и ворклет Гудини имеет простой способ проверки доступности. Это позволяет избежать проблем с использованием Гудини в браузерах, которые пока не поддерживают данную технологию.

Используйте Гудини только для улучшения отображения и визуализации


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

Используйте стандартный CSS в качестве альтернативы


Например, пользовательские свойства CSS могут использоваться как альтернатива API пользовательских свойств и значений.

Сосредоточьтесь на разработке производительных и надежных приложений, используя Гудини исключительно для декоративных целей как прогрессивное улучшение.

Заключение


Гудини позволяет разработчикам использовать JS код для работы со стилями в процессе рендеринга, повышая производительность и стабильность приложений. Возможность внедрения кода в процесс рендеринга позволяет создавать CSS полифилы, которыми легко делиться с другими, применять и, возможно, даже включать в спецификацию. Кроме того, Гудини позволяет разработчикам и дизайнерам меньше зависеть от ограничений CSS при работе со стилями, макетами и анимацией.

Гудини может использоваться уже сейчас, но только в качестве прогрессивного улучшения. Это позволит браузерам, не поддерживающим Гудини, отображать страницы без ошибок, обеспечивая оптимальный пользовательский опыт.

Жду не дождусь, когда сообщество разработчиков сможет в полной мере насладиться возможностями Гудини. Вот парочка примеров:

CSS Houdini Expirements
Интерактивное введение в CSS Houdini
Примеры использования Гудини от Google Chrome Labs

Ссылки


W3C черновики спецификации Гудини
Houdini (Chrome Dev Summit 2018)
Animation Worklet — Google Developers

Благодарю за внимание.
Tags:
Hubs:
+6
Comments5

Articles

Change theme settings