diff options
Diffstat (limited to 'files/ru/web/web_components/using_custom_elements')
-rw-r--r-- | files/ru/web/web_components/using_custom_elements/index.html | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/files/ru/web/web_components/using_custom_elements/index.html b/files/ru/web/web_components/using_custom_elements/index.html new file mode 100644 index 0000000000..bcb5b5d733 --- /dev/null +++ b/files/ru/web/web_components/using_custom_elements/index.html @@ -0,0 +1,243 @@ +--- +title: Использование пользовательских элементов +slug: Web/Web_Components/Использование_пользовательских_элементов +translation_of: Web/Web_Components/Using_custom_elements +--- +<div>{{DefaultAPISidebar("Web Components")}}</div> + +<p class="summary">Одна из ключевых особенностей стандарта Веб-компонент это возможность создавать пользовательские элементы на HTML-странице, инкапсулирующие функциональность, вместо того чтобы создавать длинyю, вложенную группу элементов, которые бы вместе реализовывали нужную пользовательскую фичу. Эта статья является вводной по использованию пользовательских HTML-компонент.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Пользовательские элементы поддерживаются по умолчанию в Firefox, Chrome и Opera. Safari пока поддерживает только автономные пользовательские компоненты, Edge также работает над реализацией.</p> +</div> + +<h2 id="Высокоуровневый_обзор">Высокоуровневый обзор</h2> + +<p>Контроллером пользовательских элементов веб-документа является объект {{domxref("CustomElementRegistry")}} — этот элемент позволяет регистрировать пользовательские элементы на веб-странице, возвращает информацию о зарегистрированных элементах и т.п.</p> + +<p>Чтобы зарегистрировать пользовательский элемент на странице, используйте метод {{domxref("CustomElementRegistry.define()")}} method. Он принимает аргументы:</p> + +<ul> + <li>{{domxref("DOMString")}} - имя элемента. Обратите внимание, что в именах пользовательских элементах <a href="https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name">должен содержаться дефис</a>; они не могут состоять только из одного слова.</li> + <li>Объект типа <a href="/en-US/docs/Web/JavaScript/Reference/Classes">class</a>, определяющий поведение элемента.</li> + <li>Опционально объект options, имеющий свойство <code>extends</code>, соответствующее встроенному элементу, от которого наследует объект.</li> +</ul> + +<p>К примеру, мы можем определить пользовательский элемент <a href="https://mdn.github.io/web-components-examples/word-count-web-component/">word-count element</a>:</p> + +<pre class="brush: js">customElements.define('word-count', WordCount, { extends: 'p' });</pre> + +<p>Этот элемент называется <code>word-count</code>, объект соответствующего класса называется <code>WordCount</code>, и он наследует элементу {{htmlelement("p")}}.</p> + +<p>Объект класса пользовательского элемента определяется с помощью синтаксиса классов ES 2015. Например, <code>WordCount</code> имеют следующую структуру:</p> + +<pre class="brush: js">class WordCount extends HTMLParagraphElement { + constructor() { + // Всегда первым делом вызывайте super() в конструкторе + super(); + + // Далее пишется функциональность элемента + + ... + } +}</pre> + +<p>Это простой пример, но его можно дополнить. Можно определить специальные lifecycle callbacks, которые будут вызваны в определенные моменты жизненного цикла элемента. Например, <code>connectedCallback</code> будет вызван, когда пользовательский элемент оказывается впервые встроен в DOM, а <code>attributeChangedCallback</code> вызывается, когда пользовательскому элементу добавляют, удаляют или изменяют какой-то аттрибут.</p> + +<p>Подробнее об этом в секции {{anch("Using the lifecycle callbacks")}} ниже.</p> + +<p>Есть два типа пользовательских элементов:</p> + +<ul> + <li><strong>Автономные пользователькие элементы</strong> независимы — они не наследуют встроенным HTML-элементам. Их используют на странице просто как обычный HTML-элемент. Например, <code><popup-info></code> или <code>document.createElement("popup-info")</code>.</li> + <li><strong>Модифицированные встроенные элементы</strong> наследуют базовым HTML-элементам. Чтобы создать один из них, вы определяете элемент, от которого они унаследованы (как в примерах выше), и используете их как обычный базовый элемент, но с добавлением атрибута/свойства с именем пользовательского элемента {{htmlattrxref("is")}}. Например <code><p is="word-count"></code>, или <code>document.createElement("p", { is: "word-count" })</code>.</li> +</ul> + +<h2 id="Разбор_простых_примеров">Разбор простых примеров</h2> + +<p>А сейчас давайте разберем еще несколько простых примеров, иллюстрирующих подробности создания пользовательских элементов.</p> + +<h3 id="Автономные_пользовательские_элементы">Автономные пользовательские элементы</h3> + +<p>Рассмотрим пример автономного пользовательского элемента — <code><a href="https://github.com/mdn/web-components-examples/tree/master/popup-info-box-web-component"><popup-info-box></a></code> (см. <a href="https://mdn.github.io/web-components-examples/popup-info-box-web-component/">работающий пример</a>). Он содержит изображение и строку, и встраивает изображение в страницу. Когда на изображение наводят фокус, компонент показывает всплывающую подсказку с текстом.</p> + +<p>Прежде всего файл JavaScript определяет класс <code>PopUpInfo</code>, наследующий от {{domxref("HTMLElement")}}. Автономные пользовательские элементы почти всегда наследуют <code>HTMLElement</code>.</p> + +<pre class="brush: js">class PopUpInfo extends HTMLElement { + constructor() { + // Всегда первым делом вызывайте super() в конструкторе + super(); + + // далее следует функциональность элемента + + ... + } +}</pre> + +<p>В этом фрагменте кода содержится определение конструктора {{jsxref("Classes.constructor","constructor")}} класса, которое всегда начинается с вызова <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/super">super()</a></code> чтобы отработала цепочка прототипного наследования.</p> + +<p>Внутри конструктора мы определяем всю функциональность, которую получит элемент при создании его объекта. В данном случае мы добавляем shadow root к пользовательскому элементу, производим манипуляции с DOM, чтобы создать определенную структуру shadow DOM внутри элемента — которая затем присоединяется к shadow root — и наконец добавляем CSS к shadow root, чтобы задать его стиль.</p> + +<pre class="brush: js">// Создание 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);</pre> + +<p>Наконец, регистрируем пользовательский элемент в <code>CustomElementRegistry</code> с помощью метода <code>define()</code>, который упоминался ранее — в качестве параметров мы передаем ему имя элемента и имя класса, который содержит его функциональность:</p> + +<pre class="brush: js">customElements.define('popup-info', PopUpInfo);</pre> + +<p>Теперь он доступен для использования на нашей странице. В HTML мы используем его так:</p> + +<pre class="brush: html"><popup-info img="img/alt.png" text="Код валидации вашей карты (CVC) + это дополнительная мера безопасности — это последние 3 или 4 цифры + на обороте вашей карты."></pre> + +<div class="note"> +<p><strong>Замечение</strong>: Вы можете прочитать <a href="https://github.com/mdn/web-components-examples/blob/master/popup-info-box-web-component/main.js">полный исходный код на JavaScript</a> здесь.</p> +</div> + +<h3 id="Модифицированные_встроенные_элементы">Модифицированные встроенные элементы</h3> + +<p>Тепреь давайте взглянем на другой пример модифицированного пользовательского элемента — <a href="https://github.com/mdn/web-components-examples/tree/master/expanding-list-web-component">раскрывающийся список</a> (<a href="https://mdn.github.io/web-components-examples/expanding-list-web-component/">см. действующий пример</a>). Он превращает любой ненумерованный список в раскрывающееся/складывающееся меню.</p> + +<p>Первым делом определим класс элемента наподобие того, как это делалось выше:</p> + +<pre class="brush: js">class ExpandingList extends HTMLUListElement { + constructor() { + // Всегда первым делом вызываем super() в конструкторе + super(); + + // ниже следует функциональность элемента + + ... + } +}</pre> + +<p>Здесь мы не будем во всех подробностях описывать функциональность элемента, вы можете понять как он работает, посмотрев исходный код. Единственное принципиальное различие с предыдующим примером состоит в том, что мы используем интерфейс {{domxref("HTMLUListElement")}}, а не {{domxref("HTMLElement")}}. Так что у него есть все характеристики элемента {{htmlelement("ul")}}, плюс дополнительная функциональность, которую определили мы. Это и отличает модифицированный встроенный элемент от автономного пользовательского элемента.</p> + +<p>Далее мы регистрируем этот элемент с помощью метода <code>define()</code> как в прошлом примере, только на сей раз мы добавляем объект options, который определяет, какому встроенному элементу наследует данный:</p> + +<pre class="brush: js">customElements.define('expanding-list', ExpandingList, { extends: "ul" });</pre> + +<p>Встроенный элемент используется на веб-странице немного по-другому:</p> + +<pre class="brush: html"><ul is="expanding-list"> + + ... + +</ul></pre> + +<p>Вы задаете элемент <code><ul></code> как обычно, но указываете имя модифицированного элемента в атрибуте <code>is</code>.</p> + +<div class="note"> +<p><strong>Замечание</strong>: Полный <a href="https://github.com/mdn/web-components-examples/blob/master/expanding-list-web-component/main.js">исходный код на JavaScript</a> доступен здесь.</p> +</div> + +<h2 id="Использование_lifecycle_callbacks">Использование lifecycle callbacks</h2> + +<p>Вы можете определить несколько разных коллбеков в конструкторе пользовательских элементов, которые сработают на разных этапах жизненного цикла элемента:</p> + +<ul> + <li><code>connectedCallback</code>: Срабатывает, когда пользовательский элемент впервые добавляется в DOM.</li> + <li><code>disconnectedCallback</code>: Срабатывает, когда пользовательский элемент удаляется из DOM.</li> + <li><code>adoptedCallback</code>: Срабатывает, когда пользовательский элемент перемещен в новый документ.</li> + <li><code>attributeChangedCallback</code>: Срабатывает, когда пользовательскому элементу добавляют, удаляют или изменяют атрибут.</li> +</ul> + +<p>Посмотрим на них в действии. Код ниже взят из примера <a href="https://github.com/mdn/web-components-examples/tree/master/life-cycle-callbacks">life-cycle-callbacks</a> (<a href="https://mdn.github.io/web-components-examples/life-cycle-callbacks/">см. его в действии</a>). Это тривиальный пример, создающий на странице цветной квадрат. Вот как выглядит код пользовательского элемента:</p> + +<pre class="brush: html"><custom-square l="100" c="red"></custom-square></pre> + +<p>Конструктор класса очень простой — мы просто добавляем shadow DOM к элементу, а затем добавляем пустые элементы {{htmlelement("div")}} и {{htmlelement("style")}} к shadow root:</p> + +<pre class="brush: js">var shadow = this.attachShadow({mode: 'open'}); + +var div = document.createElement('div'); +var style = document.createElement('style'); +shadow.appendChild(style); +shadow.appendChild(div);</pre> + +<p>Наиболее важная функция в этом примере <code>updateStyle()</code> — она принимает элемент, находит его shadow root, находит его элемент <code><style></code>, и добавляет {{cssxref("width")}}, {{cssxref("height")}}, и {{cssxref("background-color")}} к стилям.</p> + +<pre class="brush: js">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'); + } + } +}</pre> + +<p>Сами изменения стилей обрабатываются коллбеками жизненного цикла, находящимися внутри конструктора. <code>connectedCallback()</code> срабатывает, когда элемент встраивается в DOM — здесь мы запускаем функцию <code>updateStyle()</code> которая обсеспечивает, чтобы квадрат имел стиль, описанный в его атрибутах:</p> + +<pre class="brush: js">connectedCallback() { + console.log('Пользовательский элемент квадрат добавлен на страницу.'); + updateStyle(this); +}</pre> + +<p>коллбеки <code>disconnectedCallback()</code> и <code>adoptedCallback()</code> логируют простые сообщения на консоль, которые уведомляют нас, что элемент удален из DOM или перемещен на другую страницу:</p> + +<pre class="brush: js">disconnectedCallback() { + console.log('Пользователский элемент квадрат удален.'); +} + +adoptedCallback() { + console.log('Пользовательский элемент квадарат перемещен на другую страницу.'); +}</pre> + +<p>Коллбек <code>attributeChangedCallback()</code> запускается когда один из аттрибутов элемента меняется. Как видно из его свойств, можно воздействовать на индивидуальные аттрибуты, глядя на их имена, и новые и старые значения аттрибутов. В данном случае, однако, мы просто снова запускаем функцию <code>updateStyle()</code> чтобы убедиться, что атрибуты квадрата получили новые значения:</p> + +<pre class="brush: js">attributeChangedCallback(name, oldValue, newValue) { + console.log('Атрибуты пользовательского элемента квадрат изменились.'); + updateStyle(this); +}</pre> + +<p>Обратите внимание, что нужно наблюдать за атрибутами, чтобы запустить коллбек <code>attributeChangedCallback()</code> когда они изменятся. Это делается через вызов геттера <code>observedAttributes()</code> в конструкторе, который содержит оператор <code>return</code> возвращающий массив с именами атрибутов, которые вы хотите наблюдать:</p> + +<pre class="brush: js">static get observedAttributes() {return ['w', 'l']; }</pre> + +<p>В нашем случае он расположен в начале конструктора.</p> + +<div class="note"> +<p><strong>Замечение</strong>: Смотрите <a href="https://github.com/mdn/web-components-examples/blob/master/life-cycle-callbacks/main.js">полный исходный код на JavaScript</a> здесь.</p> +</div> |