Как создавать стандартные, надежные компоненты

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

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

Похоже, что два аспекта веб-компонентов противостоят друг другу: изоляция и настройка. Как разработчики могут изолировать внутренние правила CSS от внешнего мира? И как потребители могут стилизовать компоненты, чтобы добиться желаемого имиджа?

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

Для начала давайте определим цели для изоляции и настройки.

Изоляция - это брандмауэр, созданный теневой DOM компонента. При изоляции правила CSS, применяемые к веб-сайту, влияют только на элементы от корня документа до настраиваемого элемента, но не дальше. С другой стороны, правила CSS, объявленные внутри компонента, влияют только на элементы, происходящие от его дизъюнктивного теневого корня.

Это хорошая вещь. Это позволяет как разработчикам компонентов, так и потребителям компонентов независимо объявлять правила, не беспокоясь о том, что их решения будут неожиданно отменены.

Настройка - это возможность потребителя настроить внешний вид компонента. Хорошо спроектированные компоненты могут быть украшены потребителями с помощью CSS без разветвления кода.

Рабочая группа CSS уже несколько лет экспериментирует с решениями этого очевидного конфликта. Принятые ими решения теперь доступны в браузерах.

Вот что доступно и готово к использованию сегодня:

  • Пользовательские свойства CSS для передачи значений в компонент
  • Селектор :host для таргетинга на самореференциальный компонент
  • Селектор псевдокласса :host() для выбора экземпляров компонента.
  • Селектор псевдоэлементов ::slotted() для нацеливания на элементы, вставленные в компонент потребителем.

Здесь все основано на стандартах, принятых Консорциумом Всемирной паутины (W3C) и не зависит от какой-либо проприетарной инфраструктуры, такой как React, Vue или Angular. Он рассчитан на будущее.

Настраиваемые свойства CSS

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

В качестве обзора настраиваемые свойства часто объявляются на корневом уровне документа и используются в селекторах, как в этом примере:

:root {
    --color: #DDD;
    --background: #333;
}
p {
    color: var(--color);
    background-color: var(--background);
}

Используемый здесь селектор псевдокласса :root может быть кому-то незнаком. Он синонимичен селектору html, но имеет более высокую специфичность. Любой из них может быть использован.

Настраиваемые свойства необязательно объявлять в корне. Их также можно объявить в элементе. Например, мы могли бы локализовать область видимости переменных, используемых в пользовательском элементе с именем rwt-shadowbox, выполнив следующие действия:

rwt-shadowbox {
    --color: #DDD;
    --background: #333;
    --alt-color: #EEE;
    --alt-background: #222;
}

Настраиваемые свойства внутри компонентов

Теперь давайте отойдем от CSS документа и посмотрим на внутренний CSS компонента.

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

Так что, возможно, внутренняя таблица стилей компонента предназначена для использования этих имен - примерно так:

header {
    color: var(--color);
    background: var(--background);
}
footer {
    color: var(--alt-color);
    background: var(--alt-background);
}

В отличие от фиксированных значений (таких как #DDD и #333), настраиваемые свойства, подобные этому , проникают сквозь теневой брандмауэр DOM. Это основной способ для создателей компонентов предоставить потребителям контроль над их стилем.

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

Сами по себе настраиваемые свойства - хорошее начало, но мы можем сделать наши компоненты более гибкими с помощью селектора :host.

Селектор: host

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

Для этого значения переменных по умолчанию указываются внутри компонента путем объявления их в селекторе :host. Этот специальный селектор является эквивалентом теневого 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, потребитель был бы вынужден указать значения для каждой из четырех переменных.

Синтаксис CSS var() также обеспечивает резервные значения, как своего рода страховочную сетку для подобных ситуаций. Резервные значения указываются вторым параметром var(), например:

#shadowbox {
    width: var(--width, 70vw);
    height: var(--height, 50vh);
    bottom: var(--bottom, 1rem);
    left: var(--left, 1rem);
}

Выбор экземпляров компонентов

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

Создатель может сделать это с помощью селектора псевдокласса :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 компонента для таргетинга на экземпляр потребителя, обозначенный как non-member, будет выглядеть следующим образом:

: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(). Он принимает один аргумент, который является селектором, таким как имя тега, идентификатор или имя класса.

Внутренний стиль компонента для примера может выглядеть примерно так:

::slotted(#caption) {
    font-size: 2rem;
}
::slotted(p) {
    text-indent: 0;
}
::slotted(.first-para) {
    text-indent: 1rem;
}

Здесь создатель компонента использовал псевдоэлемент ::slotted() тремя способами: нацелив заголовок с помощью идентификатора, нацелив все абзацы с помощью тега <p> и выбрав только первый абзац с именем класса.

Резюме

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

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

  • Используйте настраиваемые свойства CSS, чтобы предоставить потребителю индивидуальные настройки.
  • Используйте селектор :host внутри компонентов так же, как вы используете селектор :root в документах.
  • Используйте селектор псевдоклассов :host(), чтобы дать потребителю возможность выбирать внутренние классы компонентов.
  • Используйте селектор псевдоэлементов ::slotted() для стилизации произвольных элементов, вставленных потребителем.

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