Web Components
Содержание
Intro
Web Components - это набор технологий, которые позволяют использовать компонентный подход с инкапсуляцией стилей и скриптов в вебе нативно, без подключения каких-либо библиотек или фейрмворков.
Состоит из стандартов
Custom Elements. Quite simply, these are fully-valid HTML elements with custom templates, behaviors and tag names (e.g.
<one-dialog>
) made with a set of JavaScript APIs. Custom Elements are defined in the HTML Living Standard specification.Shadow DOM. Capable of isolating CSS and JavaScript, almost like an
<iframe>
. This is defined in the Living Standard DOM specification.HTML templates. User-defined templates in HTML that aren’t rendered until called upon. The
<template>
tag is defined in the HTML Living Standard specification.HTML imports
Пользовательские элементы (Custom Elements)
Спецификация пользовательских элементов позволяет регистрировать новые теги и задавать их
поведение в соответствии с жизненным циклом — создание, вставка в DOM, изменение атрибутов, удаление из DOM.
Для того чтобы предотвратить возможный конфликт новых тегов стандарта HTML и пользовательских тегов,
имена последних обязаны содержать как минимум один дефис — например,
<custom-tag></custom-tag>
или <my-awesome-tag></my-awesome-tag>
.
Также пользовательские теги на текущий момент не могут быть самозакрывающимися, даже теги без содержимого должны быть парными.
Регистрация своего компонента
class XSpoiler extends HTMLElement {}
customElements.define("x-spoiler", XSpoiler);
customElements.define("x-spoiler", XSpoiler); // выдаст ошибку
<html>
<head></head>
<body>
<x-spoiler></x-spoiler>
</body>
</html>
Рисуем кнопку
class XSpoiler extends HTMLElement {
constructor() {
super();
let text = 'Click me'
this.innerHTML = `
<button type="button">${text}</button>
`;
this.querySelector("button").addEventListener("click", () => {
alert('You clicked me!')
});
}
}
Жизненные хуки компонента
connectedCallback
Срабатывает, когда пользовательский элемент впервые добавляется в DOM.disconnectedCallback
: Срабатывает, когда пользовательский элемент удаляется из DOM.adoptedCallback
: Срабатывает, когда пользовательский элемент перемещен в новый документ.attributeChangedCallback
: Срабатывает, когда пользовательскому элементу добавляют, удаляют или изменяют атрибут
class XSpoiler extends HTMLElement {
constructor() {
setup() // объязательный запуск функции для унаследования класса
}
// lifecycle hooks
connectedCallback() {
}
disconnectedCallback() {
}
adoptedCallback() {
}
attributeChangedCallback() {
}
}
customElements.define("x-spoiler", XSpoiler);
Компонент с слотом и ShadowDOM
class XSpoiler extends HTMLElement {
constructor() {
super();
this.text = {
"when-close": "Развернуть",
"when-open": "Свернуть"
};
this.events = {
"close": new CustomEvent("x-spoiler.changed", {
bubbles: true,
detail: {opened: false},
}),
"open": new CustomEvent("x-spoiler.changed", {
bubbles: true,
detail: {opened: true},
}),
};
// устанавливаем template по id
this.attachShadow({mode: "open"});
const template = document.getElementById("x-spoiler").content.cloneNode(true);
template.querySelector("button").textContent = this.text["when-close"];
this.shadowRoot.appendChild(template);
// вешаем click-handler
let btn = this.shadowRoot.querySelector("button")
btn.addEventListener("click", () => {
this.opened = !this.opened;
});
}
get opened() {
return this.getAttribute("opened") !== null;
}
set opened(state) {
if (!!state) {
this.setAttribute("opened", "");
} else {
this.removeAttribute("opened");
}
}
attributeChangedCallback(attrName, oldVal, newVal) {
switch (attrName) {
case "opened":
const opened = newVal !== null;
const button = this.shadowRoot.querySelector("button");
const text = this.text[opened ? "when-open" : "when-close"];
button.textContent = text;
this.dispatchEvent(this.events[opened ? "open" : "close"]);
break;
case "text-when-open":
this.text["when-open"] = newVal;
if (this.opened) {
this.shadowRoot.querySelector("button").textContent = newVal;
}
break;
case "text-when-close":
this.text["when-close"] = newVal;
if (!this.opened) {
this.shadowRoot.querySelector("button").textContent = newVal;
}
break;
}
}
static get observedAttributes() {
return [
"opened",
"text-when-open",
"text-when-close",
];
}
}
customElements.define("x-spoiler", XSpoiler);
<x-spoiler>
Содержимое тега.
</x-spoiler>
<br><br>
<x-spoiler text-when-close="Узреть" text-when-open="Скрыть">
Ещё один x-spoiler с настроенным текстом
</x-spoiler>
<br><br>
<x-spoiler opened>
Изначально открытый элемент
</x-spoiler>
<template id="x-spoiler">
<style>
:host > section {
display: none;
}
:host([opened]) > section {
display: block;
}
</style>
<button type="button"></button>
<section><slot></slot></section>
</template>