Клавиша / esc

window.customElements

Позволяет регистрировать и управлять пользовательскими HTML-элементами — Web Components

Время чтения: 6 мин

Кратко

Скопировано

window.customElements — это глобальный объект, который предоставляет методы для создания, регистрации и получения пользовательских HTML-элементов (Web Components).

Пример

Скопировано
        
          
          <my-greeting></my-greeting>
          <my-greeting></my-greeting>

        
        
          
        
      
        
          
          class MyGreeting extends HTMLElement {  connectedCallback() {    this.innerHTML = `<p>👋 Привет из кастомного элемента!</p>`;  }}customElements.define('my-greeting', MyGreeting);
          class MyGreeting extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<p>👋 Привет из кастомного элемента!</p>`;
  }
}

customElements.define('my-greeting', MyGreeting);

        
        
          
        
      
Открыть демо в новой вкладке

Как пишется

Скопировано

Объект customElements — это свойство window. Он предоставляет следующие методы:

  • .define(name, constructor, options) — регистрирует новый пользовательский элемент.
  • .get(name) — возвращает конструктор уже зарегистрированного элемента.
  • .whenDefined(name) — возвращает промис, который выполнится, когда элемент будет определён.
  • .upgrade(root) — вручную активирует пользовательские элементы внутри указанного root.
  • .getName(constructor) — возвращает имя пользовательского элемента по его конструктору, если он был зарегистрирован. Если конструктор не был зарегистрирован, возвращает undefined.

Рассмотрим подробнее каждый метод:

.define(name, constructor, options)

Скопировано

Регистрирует новый пользовательский элемент.

  • name — строка, имя нового тега (обязательно). Имя должно содержать хотя бы один дефис (-), чтобы не пересекаться с существующими HTML-тегами, например, my-element. Если имя не содержит дефиса, будет выброшено исключение. Если попытаться зарегистрировать элемент с именем, которое уже занято другим веб-компонентом, также возникнет ошибка. Подробнее о правилах именования пользовательских элементов можно прочитать в спецификации Custom Elements.

  • constructor — класс, который наследуется от HTMLElement или другого встроенного элемента (built-in HTML elements), например, HTMLButtonElement. Встроенные элементы — это стандартные элементы HTML, такие как <button>, <input>, <ul> и т.д. Наследование от них позволяет расширять их поведение. Например, если вы хотите создать свою кнопку на основе стандартной, используйте class MyButton extends HTMLButtonElement.

  • options — объект с дополнительными настройками (необязательно). Обычно используется для расширения встроенных элементов через свойство extends, например: { extends: 'button' } для создания кастомной кнопки на основе стандартной.

В чём разница между наследованием от HTMLElement и, например, HTMLButtonElement?

Скопировано

Если вы наследуетесь от HTMLElement, вы создаёте полностью новый элемент с нуля. Если от встроенного элемента (например, HTMLButtonElement), то ваш компонент будет вести себя как стандартная кнопка, но с дополнительной логикой или стилями.

.get(name)

Скопировано

Возвращает конструктор уже зарегистрированного элемента по имени. Если элемент не зарегистрирован — вернёт undefined.

.whenDefined(name)

Скопировано

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

.upgrade(root)

Скопировано

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

.getName(constructor)

Скопировано

Этот метод позволяет узнать, под каким именем был зарегистрирован пользовательский элемент по его конструктору (классу). Если переданный конструктор был зарегистрирован через customElements.define, метод вернёт строку с именем тега (например, 'my-element'). Если конструктор не был зарегистрирован как пользовательский элемент, метод вернёт undefined.

Это удобно, если у вас есть класс компонента и вы хотите узнать, зарегистрирован ли он в реестре кастомных элементов, и если да — под каким именем.

Как понять

Скопировано

Иногда стандартные HTML-элементы не подходят для ваших задач. Например:

  • Нужен сложный компонент с собственной логикой (календарь, слайдер, модальное окно)
  • Хочется переиспользовать один и тот же блок кода на разных страницах
  • Нужно инкапсулировать стили и поведение, чтобы они не конфликтовали с остальной страницей

В таких случаях вы можете создать собственный HTML-тег с помощью customElements. Это позволяет:

  • разделять код на логические блоки;
  • повторно использовать компонент на странице;
  • инкапсулировать логику и стили (если используется Shadow DOM).

Так же при создании кастомных элементом, важно знать несколько важных моментов:

  • Регистрация до использования: Рекомендуется регистрировать все пользовательские элементы с помощью customElements.define до того, как они появятся в DOM. Это можно сделать, подключив скрипт с регистрацией в <head> или в начале вашего основного JavaScript-файла, до вставки соответствующих тегов на страницу. Если элемент окажется в DOM до регистрации, браузер создаст вместо него HTMLUnknownElement, и только после регистрации выполнит «апгрейд» до нужного класса и вызовет, например, connectedCallback.
        
          
          //  До апгрейда браузером — элемент представлен как HTMLUnknownElement,//  после регистрации и апгрейда — становится экземпляром вашего классаdocument.body.innerHTML = '<my-element></my-element>';customElements.define('my-element', class extends HTMLElement {  connectedCallback() {    this.textContent = 'Привет!';  }});
          //  До апгрейда браузером — элемент представлен как HTMLUnknownElement,
//  после регистрации и апгрейда — становится экземпляром вашего класса
document.body.innerHTML = '<my-element></my-element>';

customElements.define('my-element', class extends HTMLElement {
  connectedCallback() {
    this.textContent = 'Привет!';
  }
});

        
        
          
        
      
        
          
          // Сразу становится экземпляром вашего классаcustomElements.define('my-element', class extends HTMLElement {  connectedCallback() {    this.textContent = 'Привет!';  }});document.body.innerHTML = '<my-element></my-element>';
          // Сразу становится экземпляром вашего класса
customElements.define('my-element', class extends HTMLElement {
  connectedCallback() {
    this.textContent = 'Привет!';
  }
});

document.body.innerHTML = '<my-element></my-element>';

        
        
          
        
      
  • Реакция на регистрацию с помощью whenDefined(): Если элемент уже есть в DOM, но ещё не зарегистрирован, можно использовать whenDefined(), чтобы выполнить какие-то действия сразу после его регистрации. Например, это удобно, если вы хотите заменить временный плейсхолдер на содержимое кастомного элемента:
        
          
          <!-- HTML с кастомным элементом --><custom-element>  <h1 slot="title">Кастомный элемент</h1>  <p slot="content">Контент появился после инициализации</p></custom-element>
          <!-- HTML с кастомным элементом -->
<custom-element>
  <h1 slot="title">Кастомный элемент</h1>
  <p slot="content">Контент появился после инициализации</p>
</custom-element>

        
        
          
        
      
        
          
          // Отслеживаем момент определения элементаcustomElements.whenDefined('custom-element').then(() => {  console.log('Элемент custom-element определен!');  const element = document.querySelector('custom-element');  element.setAttribute('data-defined', 'true');});// Регистрируем элементclass CustomElement extends HTMLElement {  constructor() {    super()    const shadow = this.attachShadow({ mode: "open" })    const template = document.getElementById("custom-element-template")    shadow.appendChild(template.content.cloneNode(true))  }}customElements.define("custom-element", CustomElement);
          // Отслеживаем момент определения элемента
customElements.whenDefined('custom-element').then(() => {
  console.log('Элемент custom-element определен!');

  const element = document.querySelector('custom-element');
  element.setAttribute('data-defined', 'true');
});

// Регистрируем элемент
class CustomElement extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({ mode: "open" })
    const template = document.getElementById("custom-element-template")
    shadow.appendChild(template.content.cloneNode(true))
  }
}

customElements.define("custom-element", CustomElement);

        
        
          
        
      
Открыть демо в новой вкладке
  • Именование: Названия пользовательских элементов обязательно должны содержать дефис (например, my-card, user-list), чтобы избежать конфликтов с будущими встроенными HTML-элементами.

Подсказки

Скопировано

💡 Метод upgrade() полезен для активации кастомных элементов в динамически добавленном контенте.
💡 Используйте customElements.get('element-name') для проверки, зарегистрирован ли элемент.
💡 Можно расширять не только HTMLElement, но и, например, HTMLButtonElement, указав третий параметр { extends: 'button' }, но тогда <button is="my-button"> (не <my-button>) — требуется атрибут is.

Поддержка в браузерах:
  • Chrome 67, поддерживается
  • Edge 79, поддерживается
  • Firefox 63, поддерживается
  • Safari, не поддерживается
О Baseline