Пользовательские элементы позволяют веб-разработчикам определять новые теги HTML, расширять существующие и создавать повторно используемые веб-компоненты.
С помощью пользовательских элементов веб-разработчики могут создавать новые теги HTML , расширять существующие теги HTML или расширять компоненты, созданные другими разработчиками. API — это основа веб-компонентов . Он предлагает основанный на веб-стандартах способ создания повторно используемых компонентов, используя не что иное, как ванильный JS/HTML/CSS. В результате получается меньше кода, модульный код и больше повторного использования в наших приложениях.
Введение
Браузер предоставляет нам отличный инструмент для структурирования веб-приложений. Это называется HTML. Возможно, вы слышали об этом! Он декларативный, портативный, хорошо поддерживается и с ним легко работать. Каким бы прекрасным ни был HTML, его словарный запас и расширяемость ограничены. В стандарте жизни HTML всегда отсутствовал способ автоматически связать поведение JS с вашей разметкой… до сих пор.
Пользовательские элементы — это ответ на модернизацию HTML, заполнение недостающих частей и объединение структуры с поведением. Если HTML не обеспечивает решение проблемы, мы можем создать специальный элемент, который это сделает. Пользовательские элементы учат браузер новым трюкам, сохраняя при этом преимущества HTML .
Определение нового элемента
Чтобы определить новый элемент HTML, нам нужны возможности JavaScript!
Глобальный customElements
используется для определения пользовательского элемента и обучения браузера новому тегу. Вызовите customElements.define()
указав имя тега, который вы хотите создать, и class
JavaScript, расширяющий базовый HTMLElement
.
Пример : определение панели мобильного ящика <app-drawer>
:
class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});
Пример использования:
<app-drawer></app-drawer>
Важно помнить, что использование пользовательского элемента ничем не отличается от использования <div>
или любого другого элемента. Экземпляры могут быть объявлены на странице, созданы динамически в JavaScript, могут быть прикреплены прослушиватели событий и т. д. Продолжайте читать, чтобы увидеть больше примеров.
Определение JavaScript API элемента
Функциональность пользовательского элемента определяется с помощью class
ES2015, который расширяет HTMLElement
. Расширение HTMLElement
гарантирует, что пользовательский элемент наследует весь API DOM и означает, что любые свойства/методы, которые вы добавляете в класс, становятся частью интерфейса DOM элемента. По сути, используйте этот класс для создания общедоступного API JavaScript для вашего тега.
Пример — определение интерфейса DOM <app-drawer>
:
class AppDrawer extends HTMLElement {
// A getter/setter for an open property.
get open() {
return this.hasAttribute('open');
}
set open(val) {
// Reflect the value of the open property as an HTML attribute.
if (val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
this.toggleDrawer();
}
// A getter/setter for a disabled property.
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of the disabled property as an HTML attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Can define constructor arguments if you wish.
constructor() {
// If you define a constructor, always call super() first!
// This is specific to CE and required by the spec.
super();
// Setup a click listener on <app-drawer> itself.
this.addEventListener('click', e => {
// Don't toggle the drawer if it's disabled.
if (this.disabled) {
return;
}
this.toggleDrawer();
});
}
toggleDrawer() {
// ...
}
}
customElements.define('app-drawer', AppDrawer);
В этом примере мы создаем ящик, который имеет свойство open
, свойство disabled
и метод toggleDrawer()
. Он также отражает свойства как атрибуты HTML .
Отличительной особенностью пользовательских элементов является то, что this
внутри определения класса относится к самому элементу DOM, то есть к экземпляру класса. В нашем примере this
относится к <app-drawer>
. Вот так (😉) элемент может присоединить к себе прослушиватель click
! И вы не ограничены прослушивателями событий. Весь DOM API доступен внутри кода элемента. Используйте this
для доступа к свойствам элемента, проверки его дочерних элементов ( this.children
), узлов запроса ( this.querySelectorAll('.items')
) и т. д.
Правила создания пользовательских элементов
- Имя пользовательского элемента должно содержать дефис (-) . Таким образом,
<x-tags>
,<my-element>
и<my-awesome-app>
— допустимые имена, а<tabs>
и<foo_bar>
— нет. Это требование необходимо для того, чтобы анализатор HTML мог отличать пользовательские элементы от обычных. Это также обеспечивает совместимость вперед при добавлении новых тегов в HTML. - Вы не можете зарегистрировать один и тот же тег более одного раза. Попытка сделать это приведет к возникновению исключения
DOMException
. Как только вы сообщите браузеру о новом теге, все. Никаких возвратов. - Пользовательские элементы не могут быть самозакрывающимися, поскольку HTML допускает самозакрытие только нескольких элементов . Всегда пишите закрывающий тег (
<app-drawer></app-drawer>
).
Пользовательские реакции на элементы
Пользовательский элемент может определять специальные перехватчики жизненного цикла для запуска кода в интересные моменты его существования. Это так называемые реакции на пользовательские элементы .
Имя | Вызывается, когда |
---|---|
constructor | Экземпляр элемента создается или обновляется . Полезно для инициализации состояния, настройки прослушивателей событий или создания теневого домена . См. спецификацию для ограничений на то, что вы можете делать в constructor . |
connectedCallback | Вызывается каждый раз, когда элемент вставляется в DOM. Полезно для запуска кода настройки, например для получения ресурсов или рендеринга. В общем, стоит постараться отложить работу до этого времени. |
disconnectedCallback | Вызывается каждый раз, когда элемент удаляется из DOM. Полезно для запуска кода очистки. |
attributeChangedCallback(attrName, oldVal, newVal) | Вызывается, когда наблюдаемый атрибут был добавлен, удален, обновлен или заменен. Также вызывается для начальных значений, когда элемент создается анализатором или обновляется . Примечание. Этот обратный вызов получат только атрибуты, перечисленные в свойстве observedAttributes . |
adoptedCallback | Пользовательский элемент был перемещен в новый document (например, в документ с именем document.adoptNode(el) ). |
Обратные вызовы реакций являются синхронными . Если кто-то вызывает el.setAttribute()
для вашего элемента, браузер немедленно вызовет attributeChangedCallback()
. Аналогично, вы получите disconnectedCallback()
сразу после удаления вашего элемента из DOM (например, пользователь вызывает el.remove()
).
Пример: добавление реакций пользовательского элемента в <app-drawer>
:
class AppDrawer extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// ...
}
connectedCallback() {
// ...
}
disconnectedCallback() {
// ...
}
attributeChangedCallback(attrName, oldVal, newVal) {
// ...
}
}
Определите реакции, если/когда это имеет смысл. Если ваш элемент достаточно сложен и открывает соединение с IndexedDB в connectedCallback()
, выполните необходимую работу по очистке в disconnectedCallback()
. Но будьте осторожны! Вы не можете рассчитывать на удаление вашего элемента из DOM при любых обстоятельствах. Например, disconnectedCallback()
никогда не будет вызван, если пользователь закроет вкладку.
Свойства и атрибуты
Отражение свойств в атрибуты
Свойства HTML обычно отражают свое значение обратно в DOM в виде атрибута HTML. Например, когда значения hidden
или id
изменяются в JS:
div.id = 'my-id';
div.hidden = true;
значения применяются к реальному DOM как атрибуты:
<div id="my-id" hidden>
Это называется « отражением свойств в атрибутах ». Почти каждое свойство HTML делает это. Почему? Атрибуты также полезны для декларативной настройки элемента, и некоторые API, такие как специальные возможности и селекторы CSS, полагаются на работу атрибутов.
Отражение свойства полезно везде, где вы хотите синхронизировать представление DOM элемента с его состоянием JavaScript . Одна из причин, по которой вам может потребоваться отразить свойство, заключается в том, что определяемый пользователем стиль применяется при изменении состояния JS.
Вспомните наш <app-drawer>
. Потребитель этого компонента может захотеть затемнить его и/или запретить взаимодействие с пользователем, когда он отключен:
app-drawer[disabled] {
opacity: 0.5;
pointer-events: none;
}
Когда disabled
свойство изменяется в JS, мы хотим, чтобы этот атрибут был добавлен в DOM, чтобы селектор пользователя соответствовал. Элемент может обеспечить такое поведение, отражая значение атрибута с тем же именем:
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
// Reflect the value of `disabled` as an attribute.
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
this.toggleDrawer();
}
Наблюдение за изменениями атрибутов
Атрибуты HTML — это удобный способ для пользователей объявить исходное состояние:
<app-drawer open disabled></app-drawer>
Элементы могут реагировать на изменения атрибутов, определяя attributeChangedCallback
. Браузер будет вызывать этот метод при каждом изменении атрибутов, перечисленных в массиве observedAttributes
.
class AppDrawer extends HTMLElement {
// ...
static get observedAttributes() {
return ['disabled', 'open'];
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
if (val) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
// Only called for the disabled and open attributes due to observedAttributes
attributeChangedCallback(name, oldValue, newValue) {
// When the drawer is disabled, update keyboard/screen reader behavior.
if (this.disabled) {
this.setAttribute('tabindex', '-1');
this.setAttribute('aria-disabled', 'true');
} else {
this.setAttribute('tabindex', '0');
this.setAttribute('aria-disabled', 'false');
}
// TODO: also react to the open attribute changing.
}
}
В этом примере мы устанавливаем дополнительные атрибуты в <app-drawer>
при изменении disabled
атрибута. Хотя здесь мы этого не делаем, вы также можете использовать attributeChangedCallback
, чтобы синхронизировать свойство JS с его атрибутом .
Обновления элементов
Прогрессивно улучшенный HTML
Мы уже узнали, что пользовательские элементы определяются путем вызова customElements.define()
. Но это не означает, что вам нужно определить + зарегистрировать пользовательский элемент за один раз.
Пользовательские элементы можно использовать до регистрации их определения .
Прогрессивное улучшение — это особенность пользовательских элементов. Другими словами, вы можете объявить группу элементов <app-drawer>
на странице и никогда не вызывать customElements.define('app-drawer', ...)
до гораздо более позднего времени. Это связано с тем, что браузер по-разному обрабатывает потенциальные пользовательские элементы из-за неизвестных тегов . Процесс вызова define()
и присвоения существующему элементу определения класса называется «обновлением элемента».
Чтобы узнать, когда имя тега становится определенным, вы можете использовать window.customElements.whenDefined()
. Он возвращает обещание, которое разрешается, когда элемент становится определенным.
customElements.whenDefined('app-drawer').then(() => {
console.log('app-drawer defined');
});
Пример – отложить работу до обновления набора дочерних элементов
<share-buttons>
<social-button type="twitter"><a href="...">Twitter</a></social-button>
<social-button type="fb"><a href="...">Facebook</a></social-button>
<social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');
let promises = [...undefinedButtons].map((socialButton) => {
return customElements.whenDefined(socialButton.localName);
});
// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
// All social-button children are ready.
});
Содержимое, определяемое элементом
Пользовательские элементы могут управлять своим собственным содержимым с помощью API-интерфейсов DOM внутри кода элемента. Для этого пригодятся реакции .
Пример . Создайте элемент с HTML-кодом по умолчанию:
customElements.define('x-foo-with-markup', class extends HTMLElement {
connectedCallback() {
this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
}
// ...
});
Объявление этого тега приведет к:
<x-foo-with-markup>
<b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>
// TODO: DevSite – удален пример кода, поскольку в нем использовались встроенные обработчики событий
Создание элемента, использующего Shadow DOM
Shadow DOM предоставляет элементу возможность владеть, отображать и стилизовать часть DOM, отдельную от остальной части страницы. Черт возьми, вы даже можете спрятать все приложение в одном теге:
<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>
Чтобы использовать Shadow DOM в пользовательском элементе, вызовите this.attachShadow
внутри своего constructor
:
let tmpl = document.createElement('template');
tmpl.innerHTML = `
<style>:host { ... }</style> <!-- look ma, scoped styles -->
<b>I'm in shadow dom!</b>
<slot></slot>
`;
customElements.define('x-foo-shadowdom', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
Пример использования:
<x-foo-shadowdom>
<p><b>User's</b> custom text</p>
</x-foo-shadowdom>
<!-- renders as -->
<x-foo-shadowdom>
#shadow-root
<b>I'm in shadow dom!</b>
<slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>
Пользовательский текст
// TODO: DevSite – удален пример кода, поскольку в нем использовались встроенные обработчики событий
Создание элементов из <template>
Для тех, кто не знаком, элемент <template>
позволяет объявлять фрагменты DOM, которые анализируются, инертны при загрузке страницы и могут быть активированы позже во время выполнения. Это еще один примитив API в семействе веб-компонентов. Шаблоны являются идеальным заполнителем для объявления структуры пользовательского элемента .
Пример: регистрация элемента с содержимым Shadow DOM, созданным из <template>
:
<template id="x-foo-from-template">
<style>
p { color: green; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.</p>
</template>
<script>
let tmpl = document.querySelector('#x-foo-from-template');
// If your code is inside of an HTML Import you'll need to change the above line to:
// let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');
customElements.define('x-foo-from-template', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
// ...
});
</script>
Эти несколько строк кода впечатляют. Давайте разберемся в ключевых моментах:
- Мы определяем новый элемент в HTML:
<x-foo-from-template>
- Shadow DOM элемента создается на основе
<template>
- DOM элемента является локальным для элемента благодаря Shadow DOM.
- Внутренний CSS элемента привязан к элементу благодаря Shadow DOM.
Я в Shadow DOM. Моя разметка была получена из <template>.
// TODO: DevSite – удален пример кода, поскольку в нем использовались встроенные обработчики событий
Стилизация пользовательского элемента
Даже если ваш элемент определяет свой собственный стиль с помощью Shadow DOM, пользователи могут стилизовать ваш пользовательский элемент со своей страницы. Они называются «пользовательскими стилями».
<!-- user-defined styling -->
<style>
app-drawer {
display: flex;
}
panel-item {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
panel-item:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > panel-item {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-drawer>
<panel-item>Do</panel-item>
<panel-item>Re</panel-item>
<panel-item>Mi</panel-item>
</app-drawer>
Вы можете спросить себя, как работает специфика CSS, если у элемента есть стили, определенные в Shadow DOM. С точки зрения специфичности выигрывают пользовательские стили. Они всегда будут переопределять стиль, определенный элементом. См. раздел Создание элемента, использующего Shadow DOM .
Предварительное оформление незарегистрированных элементов
Прежде чем элемент будет обновлен, вы можете указать его в CSS, используя псевдокласс :defined
. Это полезно для предварительного оформления компонента. Например, вы можете захотеть предотвратить макет или другие визуальные FOUC, скрыв неопределенные компоненты и затенив их, когда они станут определенными.
Пример — скрыть <app-drawer>
до его определения:
app-drawer:not(:defined) {
/* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
display: inline-block;
height: 100vh;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
После того, как <app-drawer>
становится определенным, селектор ( app-drawer:not(:defined)
) больше не соответствует.
Расширение элементов
API пользовательских элементов полезен для создания новых элементов HTML, а также для расширения других пользовательских элементов или даже встроенного HTML-кода браузера.
Расширение пользовательского элемента
Расширение другого пользовательского элемента осуществляется путем расширения определения его класса.
Пример . Создайте <fancy-app-drawer>
, который расширяет <app-drawer>
:
class FancyDrawer extends AppDrawer {
constructor() {
super(); // always call super() first in the constructor. This also calls the extended class' constructor.
// ...
}
toggleDrawer() {
// Possibly different toggle implementation?
// Use ES2015 if you need to call the parent method.
// super.toggleDrawer()
}
anotherMethod() {
// ...
}
}
customElements.define('fancy-app-drawer', FancyDrawer);
Расширение собственных HTML-элементов
Допустим, вы хотите создать более красивую кнопку <button>
. Вместо копирования поведения и функциональности <button>
лучшим вариантом будет постепенное улучшение существующего элемента с использованием пользовательских элементов.
Настраиваемый встроенный элемент — это настраиваемый элемент, который расширяет один из встроенных HTML-тегов браузера. Основное преимущество расширения существующего элемента — получение всех его функций (свойств DOM, методов, доступности). Нет лучшего способа написать прогрессивное веб-приложение , чем постепенно улучшать существующие элементы HTML .
Чтобы расширить элемент, вам необходимо создать определение класса, наследуемое от правильного интерфейса DOM. Например, пользовательский элемент, расширяющий <button>
должен наследовать от HTMLButtonElement
а не от HTMLElement
. Аналогично, элемент, расширяющий <img>
должен расширять HTMLImageElement
.
Пример — расширение <button>
:
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
constructor() {
super(); // always call super() first in the constructor.
this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', (e) => div.remove());
}
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
Обратите внимание, что вызов метода define()
немного меняется при расширении собственного элемента. Обязательный третий параметр сообщает браузеру, какой тег вы расширяете. Это необходимо, поскольку многие теги HTML используют один и тот же интерфейс DOM. <section>
, <address>
и <em>
(среди прочих) используют общий HTMLElement
; и <q>
, и <blockquote>
используют общий HTMLQuoteElement
; и т. д. Указав {extends: 'blockquote'}
браузер узнает, что вы создаете усовершенствованный <blockquote>
вместо <q>
. Полный список интерфейсов HTML DOM см . в спецификации HTML .
Потребители настраиваемого встроенного элемента могут использовать его несколькими способами. Они могут объявить это, добавив атрибут is=""
в собственный тег:
<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>
создайте экземпляр в JavaScript:
// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);
или используйте new
оператор:
let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;
Вот еще один пример расширения <img>
.
Пример — расширение <img>
:
customElements.define('bigger-img', class extends Image {
// Give img default size if users don't specify.
constructor(width=50, height=50) {
super(width * 10, height * 10);
}
}, {extends: 'img'});
Пользователи объявляют этот компонент как:
<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">
или создайте экземпляр в JavaScript:
const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);
Разные детали
Неизвестные элементы и неопределенные пользовательские элементы
HTML прост и гибок в работе. Например, объявите <randomtagthatdoesntexist>
на странице, и браузер с радостью его примет. Почему нестандартные теги работают? Ответ в том, что спецификация HTML позволяет это. Элементы, не определенные спецификацией, анализируются как HTMLUnknownElement
.
Этого нельзя сказать о пользовательских элементах. Потенциальные пользовательские элементы анализируются как HTMLElement
, если они созданы с допустимым именем (включая «-»). Вы можете проверить это в браузере, который поддерживает пользовательские элементы. Запустите консоль: Ctrl + Shift + J (или Cmd + Opt + J на Mac) и вставьте следующие строки кода:
// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true
// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true
Справочник по API
Глобальный объект customElements
определяет полезные методы для работы с пользовательскими элементами.
define(tagName, constructor, options)
Определяет новый пользовательский элемент в браузере.
Пример
customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});
get(tagName)
Учитывая допустимое имя тега пользовательского элемента, возвращает конструктор элемента. Возвращает undefined
, если определение элемента не было зарегистрировано.
Пример
let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();
whenDefined(tagName)
Возвращает обещание, которое разрешается при определении пользовательского элемента. Если элемент уже определен, разрешите немедленно. Отклоняется, если имя тега не является допустимым именем пользовательского элемента.
Пример
customElements.whenDefined('app-drawer').then(() => {
console.log('ready!');
});
Поддержка истории и браузера
Если вы следили за веб-компонентами последние пару лет, вы знаете, что в Chrome 36+ реализована версия API пользовательских элементов, которая использует document.registerElement()
вместо customElements.define()
. Сейчас это считается устаревшей версией стандарта, называемой v0. customElements.define()
— это новая мода, которую производители браузеров начинают внедрять. Он называется Custom Elements v1.
Если вас заинтересовала старая спецификация v0, прочтите статью о html5rocks .
Поддержка браузера
Chrome 54 ( статус ), Safari 10.1 ( статус ) и Firefox 63 ( статус ) имеют пользовательские элементы v1. Edge начал разработку .
Чтобы функция обнаруживала пользовательские элементы, проверьте наличие window.customElements
:
const supportsCustomElementsV1 = 'customElements' in window;
Полифилл
Пока поддержка браузеров не станет широко доступной, для Custom Elements v1 доступен отдельный полифил . Однако мы рекомендуем использовать загрузчик webcomComponents.js для оптимальной загрузки полифилов веб-компонентов. Загрузчик использует обнаружение функций для асинхронной загрузки только необходимых заполнений, требуемых браузером.
Установите его:
npm install --save @webcomponents/webcomponentsjs
Использование:
<!-- Use the custom element on the page. -->
<my-element></my-element>
<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>
<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
WebComponents.waitFor(() => {
// At this point we are guaranteed that all required polyfills have
// loaded, and can use web components APIs.
// Next, load element definitions that call `customElements.define`.
// Note: returning a promise causes the custom elements
// polyfill to wait until all definitions are loaded and then upgrade
// the document in one batch, for better performance.
return loadScript('my-element.js');
});
</script>
Заключение
Пользовательские элементы дают нам новый инструмент для определения новых HTML-тегов в браузере и создания повторно используемых компонентов. Объедините их с другими примитивами новой платформы, такими как Shadow DOM и <template>
, и мы начнем понимать грандиозную картину веб-компонентов:
- Кроссбраузерность (веб-стандарт) для создания и расширения повторно используемых компонентов.
- Для начала не требуется никакой библиотеки или фреймворка. Ванильный JS/HTML FTW!
- Предоставляет знакомую модель программирования. Это просто DOM/CSS/HTML.
- Хорошо работает с другими новыми функциями веб-платформы (Shadow DOM,
<template>
, пользовательские свойства CSS и т. д.). - Тесно интегрирован с DevTools браузера.
- Используйте существующие функции доступности.