CSS для веб-компонентов

CSS для веб-компонентов

Исторически сложилось так, что конструкции 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