Создание меню "вне холста" с <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>
предназначен не только для создания модальных окон, но и что его можно анимировать из любого места и в любом месте сайта, если подойти творчески. Наконец, мы рассмотрели создание веб-компонентов, позволяющих создать компонент один раз и использовать его повсюду на веб-странице.