--- title: Использование пользовательских элементов slug: Web/Web_Components/Использование_пользовательских_элементов translation_of: Web/Web_Components/Using_custom_elements ---
Одна из ключевых особенностей стандарта Веб-компонент это возможность создавать пользовательские элементы на HTML-странице, инкапсулирующие функциональность, вместо того чтобы создавать длинyю, вложенную группу элементов, которые бы вместе реализовывали нужную пользовательскую фичу. Эта статья является вводной по использованию пользовательских HTML-компонент.
Заметка: Пользовательские элементы поддерживаются по умолчанию в Firefox, Chrome и Opera. Safari пока поддерживает только автономные пользовательские компоненты, Edge также работает над реализацией.
Контроллером пользовательских элементов веб-документа является объект {{domxref("CustomElementRegistry")}} — этот элемент позволяет регистрировать пользовательские элементы на веб-странице, возвращает информацию о зарегистрированных элементах и т.п.
Чтобы зарегистрировать пользовательский элемент на странице, используйте метод {{domxref("CustomElementRegistry.define()")}} method. Он принимает аргументы:
extends
, соответствующее встроенному элементу, от которого наследует объект.К примеру, мы можем определить пользовательский элемент word-count element:
customElements.define('word-count', WordCount, { extends: 'p' });
Этот элемент называется word-count
, объект соответствующего класса называется WordCount
, и он наследует элементу {{htmlelement("p")}}.
Объект класса пользовательского элемента определяется с помощью синтаксиса классов ES 2015. Например, WordCount
имеют следующую структуру:
class WordCount extends HTMLParagraphElement { constructor() { // Всегда первым делом вызывайте super() в конструкторе super(); // Далее пишется функциональность элемента ... } }
Это простой пример, но его можно дополнить. Можно определить специальные lifecycle callbacks, которые будут вызваны в определенные моменты жизненного цикла элемента. Например, connectedCallback
будет вызван, когда пользовательский элемент оказывается впервые встроен в DOM, а attributeChangedCallback
вызывается, когда пользовательскому элементу добавляют, удаляют или изменяют какой-то аттрибут.
Подробнее об этом в секции {{anch("Using the lifecycle callbacks")}} ниже.
Есть два типа пользовательских элементов:
<popup-info>
или document.createElement("popup-info")
.<p is="word-count">
, или document.createElement("p", { is: "word-count" })
.А сейчас давайте разберем еще несколько простых примеров, иллюстрирующих подробности создания пользовательских элементов.
Рассмотрим пример автономного пользовательского элемента — <popup-info-box>
(см. работающий пример). Он содержит изображение и строку, и встраивает изображение в страницу. Когда на изображение наводят фокус, компонент показывает всплывающую подсказку с текстом.
Прежде всего файл JavaScript определяет класс PopUpInfo
, наследующий от {{domxref("HTMLElement")}}. Автономные пользовательские элементы почти всегда наследуют HTMLElement
.
class PopUpInfo extends HTMLElement { constructor() { // Всегда первым делом вызывайте super() в конструкторе super(); // далее следует функциональность элемента ... } }
В этом фрагменте кода содержится определение конструктора {{jsxref("Classes.constructor","constructor")}} класса, которое всегда начинается с вызова super()
чтобы отработала цепочка прототипного наследования.
Внутри конструктора мы определяем всю функциональность, которую получит элемент при создании его объекта. В данном случае мы добавляем shadow root к пользовательскому элементу, производим манипуляции с DOM, чтобы создать определенную структуру shadow DOM внутри элемента — которая затем присоединяется к shadow root — и наконец добавляем CSS к shadow root, чтобы задать его стиль.
// Создание shadow root var shadow = this.attachShadow({mode: 'open'}); // Создание spans var wrapper = document.createElement('span'); wrapper.setAttribute('class','wrapper'); var icon = document.createElement('span'); icon.setAttribute('class','icon'); icon.setAttribute('tabindex', 0); var info = document.createElement('span'); info.setAttribute('class','info'); // Берем содержимое атрибута и добавляем его в span var text = this.getAttribute('text'); info.textContent = text; // Вставляем иконку var imgUrl; if(this.hasAttribute('img')) { imgUrl = this.getAttribute('img'); } else { imgUrl = 'img/default.png'; } var img = document.createElement('img'); img.src = imgUrl; icon.appendChild(img); // Создаем CSS для shadow dom var style = document.createElement('style'); style.textContent = '.wrapper {' + // CSS truncated for brevity // добавляем созданные элементы к shadow dom shadow.appendChild(style); shadow.appendChild(wrapper); wrapper.appendChild(icon); wrapper.appendChild(info);
Наконец, регистрируем пользовательский элемент в CustomElementRegistry
с помощью метода define()
, который упоминался ранее — в качестве параметров мы передаем ему имя элемента и имя класса, который содержит его функциональность:
customElements.define('popup-info', PopUpInfo);
Теперь он доступен для использования на нашей странице. В HTML мы используем его так:
<popup-info img="img/alt.png" text="Код валидации вашей карты (CVC) это дополнительная мера безопасности — это последние 3 или 4 цифры на обороте вашей карты.">
Замечение: Вы можете прочитать полный исходный код на JavaScript здесь.
Тепреь давайте взглянем на другой пример модифицированного пользовательского элемента — раскрывающийся список (см. действующий пример). Он превращает любой ненумерованный список в раскрывающееся/складывающееся меню.
Первым делом определим класс элемента наподобие того, как это делалось выше:
class ExpandingList extends HTMLUListElement { constructor() { // Всегда первым делом вызываем super() в конструкторе super(); // ниже следует функциональность элемента ... } }
Здесь мы не будем во всех подробностях описывать функциональность элемента, вы можете понять как он работает, посмотрев исходный код. Единственное принципиальное различие с предыдующим примером состоит в том, что мы используем интерфейс {{domxref("HTMLUListElement")}}, а не {{domxref("HTMLElement")}}. Так что у него есть все характеристики элемента {{htmlelement("ul")}}, плюс дополнительная функциональность, которую определили мы. Это и отличает модифицированный встроенный элемент от автономного пользовательского элемента.
Далее мы регистрируем этот элемент с помощью метода define()
как в прошлом примере, только на сей раз мы добавляем объект options, который определяет, какому встроенному элементу наследует данный:
customElements.define('expanding-list', ExpandingList, { extends: "ul" });
Встроенный элемент используется на веб-странице немного по-другому:
<ul is="expanding-list"> ... </ul>
Вы задаете элемент <ul>
как обычно, но указываете имя модифицированного элемента в атрибуте is
.
Замечание: Полный исходный код на JavaScript доступен здесь.
Вы можете определить несколько разных коллбеков в конструкторе пользовательских элементов, которые сработают на разных этапах жизненного цикла элемента:
connectedCallback
: Срабатывает, когда пользовательский элемент впервые добавляется в DOM.disconnectedCallback
: Срабатывает, когда пользовательский элемент удаляется из DOM.adoptedCallback
: Срабатывает, когда пользовательский элемент перемещен в новый документ.attributeChangedCallback
: Срабатывает, когда пользовательскому элементу добавляют, удаляют или изменяют атрибут.Посмотрим на них в действии. Код ниже взят из примера life-cycle-callbacks (см. его в действии). Это тривиальный пример, создающий на странице цветной квадрат. Вот как выглядит код пользовательского элемента:
<custom-square l="100" c="red"></custom-square>
Конструктор класса очень простой — мы просто добавляем shadow DOM к элементу, а затем добавляем пустые элементы {{htmlelement("div")}} и {{htmlelement("style")}} к shadow root:
var shadow = this.attachShadow({mode: 'open'}); var div = document.createElement('div'); var style = document.createElement('style'); shadow.appendChild(style); shadow.appendChild(div);
Наиболее важная функция в этом примере updateStyle()
— она принимает элемент, находит его shadow root, находит его элемент <style>
, и добавляет {{cssxref("width")}}, {{cssxref("height")}}, и {{cssxref("background-color")}} к стилям.
function updateStyle(elem) { var shadow = elem.shadowRoot; var childNodes = shadow.childNodes; for(var i = 0; i < childNodes.length; i++) { if(childNodes[i].nodeName === 'STYLE') { childNodes[i].textContent = 'div {' + ' width: ' + elem.getAttribute('l') + 'px;' + ' height: ' + elem.getAttribute('l') + 'px;' + ' background-color: ' + elem.getAttribute('c'); } } }
Сами изменения стилей обрабатываются коллбеками жизненного цикла, находящимися внутри конструктора. connectedCallback()
срабатывает, когда элемент встраивается в DOM — здесь мы запускаем функцию updateStyle()
которая обсеспечивает, чтобы квадрат имел стиль, описанный в его атрибутах:
connectedCallback() { console.log('Пользовательский элемент квадрат добавлен на страницу.'); updateStyle(this); }
коллбеки disconnectedCallback()
и adoptedCallback()
логируют простые сообщения на консоль, которые уведомляют нас, что элемент удален из DOM или перемещен на другую страницу:
disconnectedCallback() { console.log('Пользователский элемент квадрат удален.'); } adoptedCallback() { console.log('Пользовательский элемент квадарат перемещен на другую страницу.'); }
Коллбек attributeChangedCallback()
запускается когда один из аттрибутов элемента меняется. Как видно из его свойств, можно воздействовать на индивидуальные аттрибуты, глядя на их имена, и новые и старые значения аттрибутов. В данном случае, однако, мы просто снова запускаем функцию updateStyle()
чтобы убедиться, что атрибуты квадрата получили новые значения:
attributeChangedCallback(name, oldValue, newValue) { console.log('Атрибуты пользовательского элемента квадрат изменились.'); updateStyle(this); }
Обратите внимание, что нужно наблюдать за атрибутами, чтобы запустить коллбек attributeChangedCallback()
когда они изменятся. Это делается через вызов геттера observedAttributes()
в конструкторе, который содержит оператор return
возвращающий массив с именами атрибутов, которые вы хотите наблюдать:
static get observedAttributes() {return ['w', 'l']; }
В нашем случае он расположен в начале конструктора.
Замечение: Смотрите полный исходный код на JavaScript здесь.