Исторически сложилось так, что конструкции HTML-компонентов не достаточно гибкости для настроек. Но, есть стандартный способ дать возможность потребителям удобнее настраивать компоненты.
CSS для веб-компонентов
Две стороны веб-компонентов находятся в противоречии друг с другом: изоляция и настройка. Как разработчики могут сохранить внутренние CSS-правила изолированными от внешнего мира? И как настраивать компоненты для достижения желаемого результата?
Этот конфликт можно преодолеть с помощью специальных конструкций CSS, предназначенных для веб-компонентов.
Изоляция — это брандмауэр, созданный shadow DOM компонента. В условиях изоляции правила CSS, применяемые к веб-сайту, влияют только на элементы от корня документа до пользовательского элемента, но не далее. С другой стороны, правила CSS, объявленные внутри компонента, влияют только на элементы, происходящие из его shadow root.
Это полезная вещь, она позволяет как разработчикам компонентов, так и потребителям компонентов независимо объявлять правила, не опасаясь, что их код будет конфликтовать.
С другой стороны, настройка — это способность потребителя изменять представление компонента. Хорошо продуманные компоненты вполне могут быть оформлены потребителями с помощью CSS.
Вот что доступно и готово к использованию сегодня:
- Пользовательские CSS-свойства
- Селектор
:host
- Функция псевдо-класса
:host()
- Псевдо-элемент
::slotted()
Все здесь основано на стандартах, принято консорциумом World Wide Web (W3C) и не зависит от какой-либо инфраструктуры: React, Vue или Angular и т.п. Это будущее.
Пользовательские CSS-свойства
Пользовательские CSS-свойства используются для решения множества различных проблем. Они часто объявляются на корневом уровне документа и используются в селекторах, как в этом примере:
:root {
--color: #DDD;
--background: #333;
}
p {
color: var(--color);
background-color: var(--background);
}
Используемый здесь селектор псевдо-класса :root
кому-то может быть незнаком. Он аналогичен с селектору html
, но имеет более высокую специфичность. Обычно может быть использован любой из них.
Пользовательские свойства не должны объявляться в :root
. Они могут быть объявлены для элемента. Например, мы могли бы локализовать область видимости переменных, используемых в пользовательском элементе с именем rwt-shadowbox
, так:
rwt-shadowbox {
--color: #DDD;
--background: #333;
--alt-color: #EEE;
--alt-background: #222;
}
Пользовательские свойства внутри компонентов
Теперь давайте отвлечёмся от CSS для документа и посмотрим на CSS внутри компонента.
Всякий раз, когда есть желание представить функцию наружу, CSS внутри компонента может быть разработан для использования имен пользовательских свойств. И эти имена могут быть объявлены либо в таблице стилей документа (как было показано ранее), либо внутри самого компонента.
Таблица стилей внутри компонента предназначена для использования этих имён, как-то так:
header {
color: var(--color);
background: var(--background);
}
footer {
color: var(--alt-color);
background: var(--alt-background);
}
В отличие от фиксированных значений (таких как #DDD
и #333
), свойства пользователя, преодолевают брандмауэр shadow DOM. Это основной способ для разработчиков компонентов дать потребителям возможность управлять их стилем.
Отдельные пользовательские свойства — хорошее начало, но мы можем сделать наши компоненты ещё более гибкими с помощью селектора :host
.
Селектор :host
Хороший разработчик компонентов оставит возможность изменять размеры, положение и декорации потребителю. Это можно сделать с помощью переменных, как продемонстрировано выше. Тем не менее, компоненты не должны заставлять потребителя участвовать в их разработке. Разумные значения по умолчанию всегда должны предоставляться разработчиком компонента.
Поэтому переменные по умолчанию указываются внутри компонента объявлением их в селекторе :host
. Этот специальный селектор, он для shadow DOM аналогичен селектору :root
в DOM документа.
Рассмотрим снова пример компонента под названием rwt-shadowbox
. Это диалоговое окно, разработанное с возможностью настройки. Разработчик целенаправленно использовал переменные для размера и положения компонента, чтобы предоставить потребителю гибкость в его использовании.
Для этого разработчик объявил переменные в селекторе :host
компонента и использовал их в селекторе #shadowbox
, например:
:host {
--width: 70vw;
--height: 50vh;
--bottom: 1rem;
--left: 1rem;
}
#shadowbox {
width: var(--width);
height: var(--height);
bottom: var(--bottom);
left: var(--left);
}
Если бы разработчик компонента не объявил значения по умолчанию для переменных в :host
, потребитель был бы вынужден устанавливать значения для каждой из четырёх переменных.
Синтаксис var()
предоставляет значения по умолчанию, для таких ситуаций. Они указываются в качестве второго параметра для CSS var()
:
#shadowbox {
width: var(--width, 70vw);
height: var(--height, 50vh);
bottom: var(--bottom, 1rem);
left: var(--left, 1rem);
}
Выбор экземпляров компонентов
Иногда один компонент специально разработан, чтобы иметь несколько представлений. Например, у компонента может быть темный или светлый (по умолчанию) режим. В этом случае разработчик компонента может пожелать упростить работу потребителю, указав ему имя CSS-класса, а не набор переменных для вариантов цвета.
Разработчик может сделать это с помощью селектора псевдо-класса :host()
. Например:
:host(.darkmode) {
--color: #DDD;
--background: #333;
}
:host(.brightmode) {
--color: #222;
--background: #EEE;
}
Потребитель, выбравший вариант с тёмной темой, разместит компонент в документ таким образом:
<body>
<h1>Dark mode</h1>
<rwt-shadowbox class='darkmode'></rwt-shadowbox>
</body>
Этот метод также может быть применён для использования конкретного экземпляра компонента, когда в документе их несколько. Например, есть одно диалоговое окно для зарегистрированных и немного другой вариант для незарегистрированных посетителей. CSS для использования экземпляра компонента, определенного для незарегистрированных посетителей, будет:
:host(#non-member) {
--color: #DDD;
--background: #333;
}
Потребитель, использующий компонент для незарегистрированных посетителей, укажет документ так:
<body>
<h1>Non member</h1>
<rwt-shadowbox id='non-member'></rwt-shadowbox>
</body>
Выбор отдельных элементов
Компонент может быть специально спроектирован, чтобы позволить потребителю разделять элементы
Рассмотрим вариант с тремя слотами:
<body>
<rwt-shadowbox>
<h1 slot='inner' id='caption'>Slotted Header</h2>
<p slot='inner' class='first-para'>First paragraph...</p>
<p slot='inner'>Next paragraph...</p>
</rwt-shadowbox>
</body>
До отдельных элементов можно достучаться с помощью псевдо-элемента ::slotted()
. Он принимает один аргумент, который является селектором: имя тега, id или имя класса.
Внутренний CSS компонента может выглядеть примерно так:
::slotted(#caption) {
font-size: 2rem;
}
::slotted(p) {
text-indent: 0;
}
::slotted(.first-para) {
text-indent: 1rem;
}
Здесь разработчик компонента использовал псевдо-элемент ::slotted()
тремя способами: id заголовка, все абзацы с тегом <p>
и только первый абзац с именем CSS класса.
Итого
Получилось очень много для усвоения, поэтому вот пример нескольких пользовательских компонентов.
У разработчиков в распоряжении есть четыре метода, чтобы предоставить потребителям гибкость в настройке компонентов:
- Пользовательские CSS-свойства для возможности индивидуальных настроек
- Селектор
:host
внутри компонентов, аналогичный:root
в документах - Селектор псевдо-класса
:host()
, для возможности использовать внутренние CSS-классы компонентов - Селектор псевдо-элемента
::slotted()
, чтобы стилизовать произвольные элементы
При разумном подходе конструкция компонента может пользоваться преимуществами безопасности при изоляции, обеспечивая гибкость настройки представления.
Переиспользуемые компоненты: Custom Elements, Shadow DOM и NPM