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



