Создание меню "вне холста" с <dialog> и веб-компонентами
Оглавление
- Соображения доступности для меню "вне холста"
- HTML для элемента
<dialog> - JavaScript для элемента
<dialog> - Добавление CSS для позиционирования диалога "вне холста"
- Анимация диалога "вне холста"
- Создание меню в виде веб-компонента
Основываясь на общих сведениях о меню "вне холста", мы рассмотрим шаги по созданию доступного меню "вне холста" с помощью веб-компонентов и элемента <dialog>. Мы рассмотрим, как интегрировать это меню в ваш сайт, чтобы оно не только повышало удобство использования, но и соответствовало стандартам доступности.
Соображения доступности для меню "вне холста"
Несмотря на то, что создание меню "вне холста" — довольно простая задача, если рассматривать её поверхностно, она может стать сложной, если учесть доступность. Необходимо учитывать такие моменты, как:
- Атрибут ARIA, сообщающий, что элемент расширен
expanded. - Ловушка фокуса внутри элемента "вне холста", чтобы пользователи случайно не переходили по ссылкам, которые они не видят.
- Кнопка, закрывающая элемент "вне холста" (и после этого возвращающая все атрибуты к значениям по умолчанию).
- Состояние фокуса, возвращающееся к элементу, который вызвал открытие элемента "вне холста".
Когда речь идёт о доступности, первое правило — использовать родные API браузера. В данном случае мы будем использовать элемент <dialog>.
Это позволит нам показывать содержимое элемента "вне холста" при нажатии на кнопку, предоставлять ловушку фокуса и сообщать всё, что нужно, вспомогательным технологиям. Это также означает, что нам не придётся самостоятельно поддерживать и обновлять все эти функции. В следующем разделе мы кратко рассмотрим, как создать диалоговое окно.
HTML для элемента <dialog>
Для HTML это просто: добавьте на страницу элемент <dialog>, добавьте кнопку, открывающую диалоговое окно, и, наконец, добавьте кнопку внутри окна, с помощью которой можно его закрыть:
<html>
<head></head>
<body>
All your HTML here
<button class="open-dialog">Open Dialog</dialog>
<dialog>
<button class="close-dialog">Close Dialog</dialog>
</dialog>
</body>
</html>Примечание: по умолчанию для закрытия диалога можно нажать клавишу ESC, если в нем нет кнопки закрытия.
JavaScript для элемента <dialog>
JavaScript также довольно прост. Мы создаём переменные для диалогового окна, для кнопки открытия и для кнопки закрытия. Затем мы добавляем слушатель событий для каждой из кнопок, вызывающий методы showModal() или close() для диалога:
const dialog = document.querySelector('dialog');
const buttonOpen = document.querySelector('.open-dialog')
const buttonClose = document.querySelector('.close-dialog')
buttonOpen.addEventListener('click', function() {
dialog.showModal()
});
buttonClose.addEventListener('click', function() {
dialog.close()
});Вы можете посмотреть демонстрацию этого базового элемента dialog на CodePen:
Теперь, когда элемент <dialog> создан, давайте нажмём на кнопку и посмотрим, что произойдёт. Сейчас диалог отображается в центре экрана. Это ожидаемое поведение, поскольку, когда мы обычно думаем о диалоге, мы думаем о модальной всплывающей панели, показывающей увеличенную версию изображения.
Однако в нашем случае мы хотим, чтобы меню показывалось анимировано сбоку экрана. Поэтому давайте добавим немного CSS, чтобы поместить его за пределы экрана.
Добавление CSS для позиционирования диалога "вне холста"
.dialog-menu,
.dialog-menu[open] {
position: fixed;
width: 400px;
max-width: 80%;
min-height: 100vh;
margin: 0;
margin-left: auto;
transform: translateX(100%);
transition: .3s;
}В CSS есть несколько интересных моментов. По умолчанию, когда диалог виден, он центрируется по горизонтали и вертикали, как если бы вы использовали display: flex для контейнера и margin: auto для самого элемента.
Чтобы диалог не располагался по центру экрана, мы установим margin: 0 для всех сторон, а затем margin-left: auto. Это гарантирует, что диалог будет расположен как можно дальше в правой части экрана. Затем мы используем position: fixed и transform: translateX(100%), чтобы сдвинуть диалог за пределы экрана на величину, равную его ширине (100% от его x/width).
Нам нужно, чтобы позиция: была фиксированной, иначе у нас появится горизонтальная прокрутка той же величины, что и ширина диалога. (Примечание: Мы могли бы использовать overflow-x: hidden для элемента body, но это слишком радикально и может привести к нежелательным последствиям).
Ширина установлена в 400px, но ограничена максимальной шириной в 80%. Это гарантирует, что когда диалог будет виден, он не будет занимать весь экран, и пользователи не будут удивляться, куда делся контент!
Далее, для высоты элемента установим значение не менее 100% от высоты экрана, так что если в меню много пунктов, мы сможем прокручивать его, но если их немного, диалог всё равно будет выглядеть хорошо.
Для приятного UX мы установили время перехода: 0,3 с, чтобы создать анимированный эффект для диалога, появляющегося на экране и исчезающего с него. Однако это пока не даёт никакого эффекта, потому что диалоги, которые показываются или скрываются, меняют состояние с display: none на display: block, и вы не можете анимировать переход из этих состояний. Но не волнуйтесь, у нас уже есть решение!
CSS для открытого диалогового окна
.dialog-menu[open] {
display: flex;
margin: 0;
margin-left: auto;
flex-direction: column;
transform: translateX(0);
transition: .3s;
}
.close-dialog {
margin-left: auto;
}Когда диалоговое окно становится видимым, оно автоматически получает атрибут open. Мы будем использовать этот атрибут для стилизации, когда модальное окно становится видимым.
Нам нужно снова добавить margin: 0 и margin-left: auto, так как они сбрасываются при открытии диалога и получении атрибута open. Установим display: flex, а не display: block, задаваемый браузером. Я использую flex-direction: column next, а затем устанавливаю для кнопки .close-dialog margin-left: auto, чтобы она располагалась в правой части диалога (вы можете предпочесть использовать grid или какой-либо другой механизм).
Помните, мы установили transform: translateX в -100%, чтобы расположить его вне экрана. Теперь, чтобы он оказался на экране, мы вернём значение transform: translateX(0) и добавим время перехода, чтобы он анимировался при закрытии. Да, я знаю, анимация всё ещё не работает — мы займёмся этим дальше:
Анимация диалога "вне холста"
Мы не можем анимировать что-то из display: none в display: block и наоборот. Чтобы обойти это, выполним следующие действия, кликнув по кнопке, чтобы открыть диалог:
Установим для
<dialog>display: flex, чтобы его можно было анимировать.Используем
setTimeoutна небольшой промежуток времени, а затем вызываем методshowModal(), добавляющий атрибут[open]. Теперь, поскольку свойствоdisplayдиалога имеет значениеdisplay: flexперед началом анимации, наш переход будет работать.Применяем CSS, относящийся к атрибуту
[open], для анимации диалога.const dialogMenu = document.querySelector(".dialog-menu");
const menuToggle = document.querySelector(".menu-toggle");
menuToggle.addEventListener("click", () => {
dialogMenu.style.display = "flex";
setTimeout(() => {
dialogMenu.showModal();
}, 100);
});
Когда нужно будет закрыть меню "вне холста", используем аналогичный подход.
Сначала добавим к диалогу класс .dialog-menu--closing. Затем мы можем использовать этот класс для запуска анимации отправки диалога за пределы экрана. Можно просто добавить этот класс в тот же CSS, который используется для стандартного .dialog-menu выше.
Далее используем ещё один setTimeout с очень коротким промежутком времени и используем его для:
- Закрытия диалога с помощью
.close() - Установки диалога обратно в
display: none, и - Наконец, удаления класс
dialog-menu--closing, который мы добавили, потому что анимация уже закончилась
const dialogMenu = document.querySelector(".dialog-menu");
const dialogMenuCloseButton = document.querySelector(".dialog__menu-close");
const menuToggle = document.querySelector(".menu-toggle");
dialogMenuCloseButton.addEventListener('click', () => {
dialogMenu.classList.add("dialog-menu--closing");
setTimeout(() => {
dialogMenu.close();
dialogMenu.style.display = "none";
dialogMenu.classList.remove("dialog-menu--closing");
}, 100);
}.dialog-menu,
.dialog-menu[open].dialog-menu--closing {
/* Тот же CSS, что и для .dialog-menu выше */
}Теперь у нас есть симпатичный диалог "вне холста":
Создание меню в виде веб-компонента
В этом разделе мы будем использовать веб-компонент для создания меню, поскольку веб-компоненты идеально подходят для создания многократно используемых HTML-элементов. Важно отметить, что одно обновление меню автоматически распространяется и применяется по всему сайту, так что нам нужно обновить меню только один раз. То же самое можно сделать для многоразовых хедеров и футеров.
Наш веб-компонент — это простой компонент, создающий элемент nav и помещающий в него неупорядоченный список со ссылками для нашего меню. Мы прикрепляем его к теневому DOM и добавляем немного CSS:
class Menu extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin-top: var(--spacing);
}
li + li {
border-top: 1px solid white;
}
a {
display: block;
padding-block: 1rem;
text-decoration: none;
color: white;
}
</style>
<nav>
<ul>
<li><a href="https://example.com/about">About Us</a></li>
<li><a href="https://example.com/services">Our Services</a></li>
<li><a href="https://example.com/testimonials">Testimonials</a></li>
<li><a href="https://example.com/location">Directions</a></li>
<li><a href="https://example.com/contact">Contact Us</a></li>
</ul>
</nav>
`;
}
}
customElements.define("custom-menu", Menu);Теперь, когда мы создали веб-компонент, нужно поместить его внутрь элемента <dialog>:
<dialog class="dialog-menu">
<button class="close-dialog">Close Dialog</button>
<custom-menu></custom-menu>
</dialog>Вот полная версия:
Чему мы научились
В этой статье мы узнали, как использовать нативные HTML-теги и веб API для создания перспективного сайта. Также узнали, что элемент <dialog> предназначен не только для создания модальных окон, но и что его можно анимировать из любого места и в любом месте сайта, если подойти творчески. Наконец, мы рассмотрели создание веб-компонентов, позволяющих создать компонент один раз и использовать его повсюду на веб-странице.