Создание веб-компонента с нуля
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"] {
/* Стили скрытого контента */
}