This page was translated from English by the community.Learn more and join the MDN Web Docs community.
Использование пользовательских элементов
Одна из ключевых особенностей стандарта Веб-компонент это возможность создавать пользовательские элементы на HTML-странице, инкапсулирующие функциональность, вместо того чтобы создавать длинную, вложенную группу элементов, которые бы вместе реализовывали нужную пользовательскую фичу. Эта статья является вводной по использованию пользовательских HTML-компонент.
Примечание:Пользовательские элементы поддерживаются по умолчанию в Firefox, Chrome и Opera. Safari пока поддерживает только автономные пользовательские компоненты, Edge также работает над реализацией.
In this article
Высокоуровневый обзор
Контроллером пользовательских элементов веб-документа является объектCustomElementRegistry — этот элемент позволяет регистрировать пользовательские элементы на веб-странице, возвращает информацию о зарегистрированных элементах и т.п.
Чтобы зарегистрировать пользовательский элемент на странице, используйте методCustomElementRegistry.define() method. Он принимает аргументы:
DOMString- имя элемента. Обратите внимание, что в именах пользовательских элементахдолжен содержаться дефис; они не могут состоять только из одного слова.- Объект типаclass, определяющий поведение элемента.
- Опционально объект options, имеющий свойство
extends, соответствующее встроенному элементу, от которого наследует объект.
К примеру, мы можем определить пользовательский элементword-count element:
customElements.define("word-count", WordCount, { extends: "p" });Этот элемент называетсяword-count, объект соответствующего класса называетсяWordCount, и он наследует элементу<p>.
Объект класса пользовательского элемента определяется с помощью синтаксиса классов ES 2015. Например,WordCount имеют следующую структуру:
class WordCount extends HTMLParagraphElement { constructor() { // Всегда первым делом вызывайте super() в конструкторе super(); // Далее пишется функциональность элемента ... }}Это простой пример, но его можно дополнить. Можно определить специальные lifecycle callbacks, которые будут вызваны в определённые моменты жизненного цикла элемента. Например,connectedCallback будет вызван, когда пользовательский элемент оказывается впервые встроен в DOM, аattributeChangedCallback вызывается, когда пользовательскому элементу добавляют, удаляют или изменяют какой-то атрибут.
Подробнее об этом в секцииUsing the lifecycle callbacks ниже.
Есть два типа пользовательских элементов:
- Автономные пользовательские элементы независимы — они не наследуют встроенным HTML-элементам. Их используют на странице просто как обычный HTML-элемент. Например,
<popup-info>илиdocument.createElement("popup-info"). - Модифицированные встроенные элементы наследуют базовым HTML-элементам. Чтобы создать один из них, вы определяете элемент, от которого они унаследованы (как в примерах выше), и используете их как обычный базовый элемент, но с добавлением атрибута/свойства с именем пользовательского элемента
is. Например<p is="word-count">, илиdocument.createElement("p", { is: "word-count" }).
Разбор простых примеров
А сейчас давайте разберём ещё несколько простых примеров, иллюстрирующих подробности создания пользовательских элементов.
Автономные пользовательские элементы
Рассмотрим пример автономного пользовательского элемента —<popup-info-box> (см.работающий пример). Он содержит изображение и строку, и встраивает изображение в страницу. Когда на изображение наводят фокус, компонент показывает всплывающую подсказку с текстом.
Прежде всего файл JavaScript определяет классPopUpInfo, наследующий отHTMLElement. Автономные пользовательские элементы почти всегда наследуютHTMLElement.
class PopUpInfo extends HTMLElement { constructor() { // Всегда первым делом вызывайте super() в конструкторе super(); // далее следует функциональность элемента ... }}В этом фрагменте кода содержится определение конструктораconstructor класса, которое всегда начинается с вызоваsuper() чтобы отработала цепочка прототипного наследования.
Внутри конструктора мы определяем всю функциональность, которую получит элемент при создании его объекта. В данном случае мы добавляем shadow root к пользовательскому элементу, производим манипуляции с DOM, чтобы создать определённую структуру shadow DOM внутри элемента — которая затем присоединяется к shadow root — и наконец добавляем CSS к shadow root, чтобы задать его стиль.
// Создание shadow rootvar shadow = this.attachShadow({ mode: "open" });// Создание spansvar 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");// Берём содержимое атрибута и добавляем его в spanvar 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 domvar 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 цифры на обороте вашей карты."></popup-info>Примечание:Вы можете прочитатьполный исходный код на JavaScript здесь.
Модифицированные встроенные элементы
Теперь давайте взглянем на другой пример модифицированного пользовательского элемента —раскрывающийся список (см. действующий пример). Он превращает любой ненумерованный список в раскрывающееся/складывающееся меню.
Первым делом определим класс элемента наподобие того, как это делалось выше:
class ExpandingList extends HTMLUListElement { constructor() { // Всегда первым делом вызываем super() в конструкторе super(); // ниже следует функциональность элемента ... }}Здесь мы не будем во всех подробностях описывать функциональность элемента, вы можете понять как он работает, посмотрев исходный код. Единственное принципиальное различие с предыдущим примером состоит в том, что мы используем интерфейсHTMLUListElement, а неHTMLElement. Так что у него есть все характеристики элемента<ul>, плюс дополнительная функциональность, которую определили мы. Это и отличает модифицированный встроенный элемент от автономного пользовательского элемента.
Далее мы регистрируем этот элемент с помощью методаdefine() как в прошлом примере, только на сей раз мы добавляем объект options, который определяет, какому встроенному элементу наследует данный:
customElements.define("expanding-list", ExpandingList, { extends: "ul" });Встроенный элемент используется на веб-странице немного по-другому:
<ul is="expanding-list"> ...</ul>Вы задаёте элемент<ul> как обычно, но указываете имя модифицированного элемента в атрибутеis.
Примечание:Полныйисходный код на JavaScript доступен здесь.
Использование lifecycle callbacks
Вы можете определить несколько разных колбэков в конструкторе пользовательских элементов, которые сработают на разных этапах жизненного цикла элемента:
connectedCallback: Срабатывает, когда пользовательский элемент впервые добавляется в DOM.disconnectedCallback: Срабатывает, когда пользовательский элемент удаляется из DOM.adoptedCallback: Срабатывает, когда пользовательский элемент перемещён в новый документ.attributeChangedCallback: Срабатывает, когда пользовательскому элементу добавляют, удаляют или изменяют атрибут.
Посмотрим на них в действии. Код ниже взят из примераlife-cycle-callbacks (см. его в действии). Это тривиальный пример, создающий на странице цветной квадрат. Вот как выглядит код пользовательского элемента:
<custom-square l="100" c="red"></custom-square>Конструктор класса очень простой — мы просто добавляем shadow DOM к элементу, а затем добавляем пустые элементы<div> и<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>, и добавляетwidth,height, и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 здесь.