Современный веб развивается бешеными темпами. Фреймворки появляются и исчезают, а стандарты остаются. Веб-компоненты — это набор технологий, который позволяет создавать собственные HTML-теги с инкапсулированной логикой и стилями. И всё это без единой строчки сторонних библиотек.

Digital-студия WNDER

Что такое веб-компоненты и зачем они нужны

Представьте, что вы можете создать свой собственный тег <my-button> и использовать его на любой странице. Браузер поймёт его, отрисует кнопку с нужными стилями и поведением. Никаких React, Vue или Angular. Только стандартные возможности браузера.

Веб-компоненты решают главную проблему современной разработки — переиспользование кода. Вы пишете компонент один раз, а затем вставляете его где угодно. Стили не конфликтуют, потому что они инкапсулированы. JavaScript-логика изолирована. Это как LEGO: собрал деталь и используешь в разных конструкциях.

Основные технологии:

  • Custom Elements — позволяют определять свои HTML-теги.
  • Shadow DOM — изолирует стили и разметку внутри компонента.
  • HTML Templates — шаблоны, которые не рендерятся до момента использования.
  • ES Modules — импорт и экспорт кода между файлами.

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

Начнём с простого. Сделаем кнопку, которая при клике показывает всплывающее сообщение. Но не обычную, а веб-компонент.

Сначала определим класс, который наследуется от HTMLElement. В конструкторе вызовем super() и создадим Shadow DOM.

class MyButton extends HTMLElement {
  constructor() {
    super();
    // Создаём Shadow DOM
    this.attachShadow({ mode: 'open' });
    // Создаём кнопку
    const button = document.createElement('button');
    button.textContent = 'Нажми меня';
    // Добавляем стили
    const style = document.createElement('style');
    style.textContent = `
      button {
        background: #4CAF50;
        color: white;
        border: none;
        padding: 10px 20px;
        border-radius: 5px;
        cursor: pointer;
        font-size: 16px;
      }
      button:hover {
        background: #45a049;
      }
    `;
    // Обработчик клика
    button.addEventListener('click', () => {
      alert('Привет из веб-компонента!');
    });
    // Собираем всё вместе
    this.shadowRoot.append(style, button);
  }
}

// Регистрируем элемент
customElements.define('my-button', MyButton);

Теперь в HTML можно просто написать <my-button></my-button>, и браузер отрисует стилизованную кнопку. Стили внутри Shadow DOM не влияют на остальную страницу, и наоборот.

Правило: Всегда используйте mode: 'open' в attachShadow, если хотите иметь доступ к Shadow DOM извне через element.shadowRoot. Закрытый режим (mode: 'closed') делает shadow root недоступным, что усложняет отладку.

Жизненный цикл веб-компонента

У Custom Elements есть специальные методы, которые вызываются на разных этапах жизни. Их называют «реакциями» (reactions).

Метод Когда вызывается
constructor() При создании экземпляра элемента
connectedCallback() Когда элемент добавлен в DOM
disconnectedCallback() Когда элемент удалён из DOM
attributeChangedCallback() При изменении атрибута из списка observedAttributes
adoptedCallback() При перемещении элемента в новый документ

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

class MyButton extends HTMLElement {
  static get observedAttributes() {
    return ['label', 'color'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'label') {
      this.shadowRoot.querySelector('button').textContent = newValue;
    }
    if (name === 'color') {
      this.shadowRoot.querySelector('button').style.backgroundColor = newValue;
    }
  }
}

Теперь можно менять текст и цвет кнопки через атрибуты: <my-button label="Отправить" color="#ff5722"></my-button>.

Использование шаблонов и слотов

Вместо создания элементов в JavaScript можно использовать тег <template>. Он не отображается на странице, но его содержимое доступно для клонирования.

<template id="my-button-template">
  <style>
    button {
      background: #4CAF50;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 5px;
      cursor: pointer;
    }
  </style>
  <button><slot></slot></button>
</template>

Здесь <slot> — это место, куда будет вставлен контент из основного документа. Например:

<my-button>Нажми меня</my-button>

Текст «Нажми меня» попадёт внутрь кнопки. Это называется «слотами» (slots) и позволяет делать компоненты гибкими.

В JavaScript используем шаблон так:

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const template = document.getElementById('my-button-template');
    const clone = template.content.cloneNode(true);
    this.shadowRoot.appendChild(clone);
  }
}
customElements.define('my-button', MyButton);
Лайфхак: Если нужно, чтобы слот имел запасное содержимое, просто добавьте его внутрь <slot>Резервный текст</slot>. Оно отобразится, если в компонент не передали контент.

Стилизация и инкапсуляция

Одно из главных преимуществ Shadow DOM — изоляция стилей. Стили, определённые внутри компонента, не влияют на внешний мир, и наоборот. Но иногда нужно стилизовать сам хост-элемент (например, <my-button>). Для этого есть селектор :host.

const style = document.createElement('style');
style.textContent = `
  :host {
    display: inline-block;
    margin: 5px;
  }
  button {
    /* стили кнопки */
  }
`;

Также можно использовать CSS-переменные для темизации:

style.textContent = `
  button {
    background: var(--button-bg, #4CAF50);
    color: var(--button-color, white);
  }
`;

Теперь снаружи можно задать --button-bg: red; и кнопка станет красной.

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

  • Не злоупотребляйте Shadow DOM. Если компонент простой, может быть достаточно обычного класса.
  • Следите за производительностью. Частые изменения в attributeChangedCallback могут вызвать лишние перерисовки.
  • Используйте customElements.whenDefined() для ожидания регистрации компонента.
  • Помните о полифилах. Старые браузеры (IE11) не поддерживают веб-компоненты, но для них есть полифилы.

Вот пример асинхронной загрузки компонента:

customElements.whenDefined('my-button').then(() => {
  console.log('Компонент готов к использованию');
});

Ещё один важный момент: если вы используете сборщики (Webpack, Rollup), убедитесь, что они корректно обрабатывают веб-компоненты. Иногда требуется дополнительная настройка.

Заключение

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

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

Студия WNDER