Создание веб-компонента с нуля
HTML
Сердце HTML веб-компонента — HTML.
При таком подходе идея заключается в том, чтобы начать с базовой версии полностью функционального HTML, которую вы превратите в нечто более интерактивное после определения веб-компонента.
Для этого добавим button, показывающий или скрывающий контент, и div с контентом. Поскольку мы не хотим, чтобы кнопка отображалась пока веб-компонент не будет готов, добавим атрибут [hidden], чтобы скрыть её.
<button hidden>Show Content</button>
<div>
<p>Now you see me, now you don't!</p>
</div>Теперь у нас есть контент, видимый по умолчанию.
Давайте обернём его в пользовательский элемент, который назовём show-hide. Также добавим атрибут [trigger] к кнопке button и [content] к контенту.
Наш веб-компонент использует их после своего определения для определения переключателя и содержимого, соответственно. Это позволяет нам поместить button до или после содержимого и даёт нам некоторую гибкость.
<show-hide>
<button trigger hidden>Show Content</button>
<div content>
<p>Now you see me, now you don't!</p>
</div>
</show-hide>Определение веб-компонента
Теперь, когда у нас есть базовый HTML, определим наш веб-компонент с помощью метода customElements.define().
Определим наш пользовательский элемент show-hide и расширим класс HTMLElement. В constructor() используем метод super(), чтобы получить доступ к свойствам родительского класса.
customElements.define('show-hide', class extends HTMLElement {
/**
* Определение веб-компонента
*/
constructor () {
// Получение свойств родительского класса
super();
}
});Определение свойств
Далее давайте определим свойства.
С помощью метода Element.querySelector() найдём элементы [trigger] и [content] внутри пользовательского элемента (this) и присвоим их свойствам trigger и content соответственно.
Если элементов не существует, вызываем return, чтобы закончить настройку раньше времени.
/**
* Определение веб-компонента
*/
constructor () {
// Получение свойств родительского класса
super();
// Получение элементов
this.trigger = this.querySelector('[trigger]');
this.content = this.querySelector('[content]');
if (!this.trigger || !this.content) return;
}Настройка DOM
Определив свойства, перейдём к работе с DOM и настраивать слушатель событий.
Сначала воспользуемся методом Element.removeAttribute(), для удаления атрибута [hidden] из элемента button, this.trigger.
Также воспользуемся методом Element.setAttribute(), чтобы добавить атрибут [aria-expanded] со значением false. Это сообщит устройствам чтения с экрана, что кнопка переключает видимость содержимого, и каково текущее состояние этого содержимого.
/**
* Определение веб-компонента
*/
constructor () {
// Получение свойств родительского класса
super();
// Получение элементов
this.trigger = this.querySelector('[trigger]');
this.content = this.querySelector('[content]');
if (!this.trigger || !this.content) return;
// Настройка UI по умолчанию
this.trigger.removeAttribute('hidden');
this.trigger.setAttribute('aria-expanded', false);
}Затем добавим атрибут [hidden] к элементу this.content, чтобы скрыть его.
Затем добавим слушателя события click к элементу this.trigger. Используем метод handleEvent(), встроенный в Web-компоненты, для обработки нашего события (подробнее об этом чуть позже), и передадим this в качестве обратного вызова.
/**
* Определение веб-компонента
*/
constructor () {
// ...
// Настройка UI по умолчанию
this.trigger.removeAttribute('hidden');
this.trigger.setAttribute('aria-expanded', false);
this.content.setAttribute('hidden', '');
// Слушаем событие click
this.trigger.addEventListener('click', this);
}Обработка событий
Метод handleEvent() — часть API EventListener, и существует уже десятки лет.
Если вы прослушиваете событие с помощью метода addEventListener(), то в качестве второго аргумента можно передать не функцию обратного вызова, а объект.
Пока у этого объекта есть метод handleEvent(), событие будет передано в него, но при этом сохранится привязка к объекту.
customElements.define('show-hide', class extends HTMLElement {
/**
* Определение веб-компонента
*/
constructor () {
// ...
}
/**
* Обработка событий в веб-компоненте
* @param {Event} event объект Event
*/
handleEvent (event) {
// Обработка события...
}
});Внутри метода handleEvent() сначала запустим метод event.preventDefault(), чтобы убедиться, что кнопка не вызовет никаких неожиданных побочных эффектов, например отправки формы.
/**
* Обработка событий в веб-компоненте
* @param {Event} event объект Event
*/
handleEvent (event) {
// Не позволяем кнопке запускать другие действия
event.preventDefault();
}Затем воспользуемся методом Element.getAttribute(), чтобы получить значение атрибута [aria-expanded] на элементе this.trigger.
Если это значение равно true, то содержимое развёрнуто и его следует скрыть. Если нет, то оно скрыто и его следует показать.
Мы установим или уберём атрибут [hidden] у this.content, соответственно, и обновим значение атрибута [aria-expanded], чтобы оно соответствовало текущему состоянию.
/**
* Обработка событий в веб-компоненте
* @param {Event} event объект Event
*/
handleEvent (event) {
// Не позволяем кнопке запускать другие действия
event.preventDefault();
// Если содержимое развёрнуто, скроем его
// В противном случае покажем его
if (this.trigger.getAttribute('aria-expanded') === 'true') {
this.trigger.setAttribute('aria-expanded', false);
this.content.setAttribute('hidden', '');
} else {
this.trigger.setAttribute('aria-expanded', true);
this.content.removeAttribute('hidden');
}
}Теперь при переключении кнопки содержимое будет отображаться или скрываться.
Стилизация
Приятным моментом в использовании соответствующих ARIA-атрибутов (например, [aria-expanded]) для интерактивных элементов то, что их можно использовать для стилизации элементов в зависимости от текущего состояния элемента.
Например, вы можете использовать атрибут [aria-expanded], чтобы показывать значки на кнопке в зависимости от того, видно содержимое или нет.
show-hide [aria-expanded="true"] {
/* Стили видимого контента */
}
show-hide [aria-expanded="false"] {
/* Стили скрытого контента */
}