Настраиваемые встроенные элементы (Customized Built-in Elements)
📅
Урок: Настраиваемые встроенные элементы (Customized Built-in Elements)
Зачем они нужны?
До этого момента мы создавали автономные пользовательские элементы (autonomous custom elements), такие как <time-formatted>. Они являются независимыми и не несут никакой семантической нагрузки для браузера. Поисковые системы не понимают их предназначения, а вспомогательные технологии (например, скринридеры для слабовидящих) не знают, как с ними работать.
Но что, если нам нужно создать специализированную кнопку, улучшенный список или расширенное текстовое поле? Было бы неразумно отказываться от всех встроенных возможностей и семантики стандартных HTML-элементов. Именно для этого существуют настраиваемые встроенные элементы.
Они позволяют наследовать функциональность и поведение встроенных элементов HTML (таких как <button>, <input>, <ul>), добавляя к ним новую логику, стили и возможности. Вы создаете элемент, который "смешивает" мощь стандартного тега с вашей кастомной реализацией.
Как создать настраиваемый встроенный элемент
Процесс похож на создание автономного элемента, но с несколькими ключевыми отличиями.
1. Наследуем класс встроенного элемента
Вместо наследования от базового HTMLElement, ваш класс должен наследоваться от класса конкретного HTML-элемента, который вы хотите расширить, например, HTMLButtonElement, HTMLInputElement, HTMLAnchorElement и т.д.
// Создаём класс для кнопки с анимацией
class PlasticButton extends HTMLButtonElement {
constructor() {
super(); // всегда первым делом вызываем super()
this.addEventListener('click', () => {
// Здесь код для крутой анимации при клике!
console.log('✨ Проигрываем анимацию!');
});
}
// Можно добавлять свои методы и хуки жизненного цикла
connectedCallback() {
console.log('Пластиковая кнопка добавлена в документ!');
}
}
2. Регистрируем элемент с указанием тега
При регистрации элемента с помощью customElements.define() мы добавляем третий аргумент — объект с опцией extends. Это обязательно, так как многие разные теги (например, <q> и <blockquote>) используют один и тот же DOM-интерфейс (HTMLQuoteElement). Браузеру нужно точно знать, какой именно тег вы расширяете.
// Регистрируем элемент, указывая, что он расширяет тег <button>
customElements.define('plastic-button', PlasticButton, { extends: 'button' });
3. Используем элемент в HTML
Чтобы использовать наш новый элемент, мы не создаём новый тег. Вместо этого мы пишем стандартный тег, но добавляем ему атрибут is, значение которого — имя вашего настраиваемого элемента.
Правильно:
<button is="plastic-button">Нажми меня!</button>
Неправильно:
<!-- Это будет просто неизвестный браузеру тег без какой-либо семантики -->
<plastic-button>Не делай так</plastic-button>
Ваша кнопка теперь сохраняет все стандартные свойства и поведение обычной кнопки: она может получать фокус, участвовать в отправке форм, быть отключенной с помощью атрибута disabled, и браузер применяет к ней стандартные стили.
<button is="plastic-button" disabled>Отключенная пластиковая кнопка</button>
Создание элемента через JavaScript
Вы можете создать настраиваемый встроенный элемент программно двумя способами.
1. С помощью document.createElement():
Для этого используется специальная форма метода, принимающая второй аргумент — объект с опцией is.
const plasticButton = document.createElement('button', { is: 'plastic-button' });
plasticButton.textContent = 'Я создан в JS';
document.body.append(plasticButton);
2. Через конструктор класса:
Вы можете создать экземпляр напрямую, вызвав new YourClass().
const plasticButton2 = new PlasticButton();
console.log(plasticButton2.localName); // Выведет "button" (базовый тег)
Интересный нюанс: при программном создании атрибут is физически не добавляется в DOM до тех пор, пока элемент не будет сериализован (например, через outerHTML).
console.log(plasticButton.hasAttribute('is')); // false
console.log(plasticButton.outerHTML); // Выведет '<button is="plastic-button">Я создан в JS</button>'
Важные ограничения
Настраиваемые встроенные элементы могут расширять только современные, стандартные HTML-элементы (например, button, input, div, img). Нельзя расширять устаревшие элементы, такие как <bgsound>, <blink> или <marquee>, поскольку их классом является HTMLUnknownElement.
Это ограничение обеспечивает совместимость с будущими версиями HTML. Если бы разработчики могли расширять произвольные имена тегов, это могло бы заблокировать внедрение новых официальных элементов с таким же именем в будущем.
Итоги
| Аспект | Автономный элемент (например, <time-formatted>) |
Настраиваемый встроенный элемент (например, <button is="plastic-button">) |
|---|---|---|
| Наследование | class extends HTMLElement |
class extends HTMLButtonElement |
| Регистрация | define('my-element', MyElement) |
define('my-element', MyElement, {extends: 'button'}) |
| Семантика | Нет (новая семантика задаётся вручную) | Да (наследуется от базового элемента) |
| Использование | <my-element> |
<button is="my-element"> |
| Поведение | "Чистый лист", вы задаёте всё сами | Наследует всё поведение базового элемента (фокус, формы, стили) |
Для создания сложных UI-компонентов, которые должны быть доступными и вести себя как стандартные элементы (кнопки, поля ввода, списки), всегда предпочтительнее использовать пользовательские встроенные элементы. Автономные элементы хороши для создания совершенно новых, уникальных виджетов, не имеющих аналогов в стандартном HTML.