Веб-компоненты — это способ создавать собственные HTML-теги, которые работают в любом современном браузере без фреймворков. Если вы устали от зоопарка библиотек и хотите писать переиспользуемые элементы на чистом JavaScript, эта статья для вас. Разберём, как собрать компонент с нуля, зачем нужен Shadow DOM и какие подводные камни вас ждут.

Digital-студия WNDER

Что такое веб-компоненты и с чем их едят

Веб-компоненты — это набор стандартов W3C, который позволяет создавать кастомные HTML-элементы. Представьте, что вы хотите использовать тег <my-button> так же просто, как <button>. Веб-компоненты дают такую возможность. Они состоят из трёх основных технологий:

  • Custom Elements — позволяют определять новые HTML-теги и их поведение.
  • Shadow DOM — изолирует стили и разметку компонента от остальной страницы.
  • HTML Templates — теги <template> и <slot> для декларативного описания структуры.

Всё это работает нативно в Chrome, Firefox, Safari и Edge. Никаких транспиляторов и сборщиков не требуется (хотя они могут пригодиться для оптимизации).

Создаём первый кастомный элемент

Начнём с простого: компонент, который выводит приветствие. Определим класс, унаследованный от HTMLElement, и зарегистрируем его через customElements.define.

class HelloWorld extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `

Привет, мир!

`; } } customElements.define('hello-world', HelloWorld);

Теперь в HTML можно писать <hello-world></hello-world> — и браузер отрисует приветствие. Обратите внимание: имя тега должно содержать дефис, чтобы не пересекаться со стандартными элементами.

Метод connectedCallback вызывается, когда элемент появляется в документе. Есть и другие хуки жизненного цикла: disconnectedCallback (удаление), attributeChangedCallback (изменение атрибутов) и adoptedCallback (перемещение в другой документ).

Shadow DOM: изоляция по-взрослому

Одна из главных болей при разработке компонентов — конфликты стилей. Shadow DOM решает эту проблему: стили внутри компонента не протекают наружу, а внешние стили не влияют на внутренности. Создать теневой корень просто:

class FancyButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      
      
    `;
  }
}
customElements.define('fancy-button', FancyButton);

Теперь используем так: <fancy-button>Купить</fancy-button>. Внутренняя кнопка получит стили, но они не затронут другие кнопки на странице. Атрибут mode: 'open' позволяет получить доступ к теневому дереву через element.shadowRoot (для отладки). Если хотите полностью закрыть доступ, используйте mode: 'closed'.

Шаблоны и слоты: делаем компоненты гибкими

Чтобы компонент можно было настраивать, используйте тег <template> и слоты. Шаблон позволяет описать разметку один раз и клонировать её при необходимости. Слоты — это точки вставки пользовательского контента.

<template id="user-card-template">
  <style>
    .card {
      border: 1px solid #ccc;
      border-radius: 8px;
      padding: 16px;
      max-width: 300px;
    }
    .avatar {
      width: 50px;
      height: 50px;
      border-radius: 50%;
      background: #eee;
    }
  </style>
  <div class="card">
    <div class="avatar"></div>
    <h3><slot name="username">Аноним</slot></h3>
    <slot name="bio"></slot>
  </div>
</template>

Использование:

class UserCard extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById('user-card-template');
    const content = template.content.cloneNode(true);
    this.attachShadow({ mode: 'open' }).appendChild(content);
  }
}
customElements.define('user-card', UserCard);

В HTML пишем:

<user-card>
  <span slot="username">Иван</span>
  <span slot="bio">Люблю JavaScript и котиков</span>
</user-card>

Компонент подставит переданные данные в нужные места. Если слот не заполнен, отобразится контент по умолчанию (например, «Аноним»).

Практические советы и подводные камни

Вот несколько рекомендаций, которые сэкономят вам время и нервы.

Правило: всегда используйте префикс в имени компонента (например, x- или my-), чтобы избежать конфликтов с будущими стандартными элементами. Например, x-modal вместо modal.

Также помните о следующих моментах:

  • Не злоупотребляйте Shadow DOM. Если компонент простой, возможно, хватит и обычного DOM с уникальными классами.
  • Следите за производительностью. Каждый экземпляр компонента создаёт теневой корень — это дополнительные накладные расходы. Не создавайте тысячи компонентов без необходимости.
  • Используйте observedAttributes и attributeChangedCallback для реакции на изменение атрибутов.
  • Помните о серверном рендеринге. Веб-компоненты не работают без JavaScript, поэтому для SEO-критичных страниц лучше использовать прогрессивное улучшение.

Сравним основные методы жизненного цикла:

Метод Когда вызывается Что делать
constructor При создании элемента Инициализация, создание Shadow DOM
connectedCallback После добавления в документ Отрисовка, подписка на события
disconnectedCallback При удалении из документа Очистка таймеров, отписка от событий
attributeChangedCallback При изменении атрибута Обновление состояния

Что в итоге

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

Главное — экспериментируйте и не бойтесь заглядывать в документацию. Удачи в создании собственных HTML-элементов!

Студия WNDER