Качественную отзывчивую систему навигации создать не так просто, как может казаться на первый взгляд. Некоторые пользователи активно используют клавиатуру, у некоторых огромные экраны монитора, а некоторые будут заходить на страницу с небольшого мобильного устройства. У каждого посетителя должен быть доступ к меню с возможностью открывать и закрывать панель навигации.
Веб-тактика
В компоненте выезжающей боковой панели (sidebar) предстоит объединить несколько важных функций веб-платформы:
- CSS
:target
- CSS сетка
- CSS преобразования
- CSS Media Queries для области просмотра и пользовательских предпочтений
- JS для
focus
(UX-улучшение)
В рассматриваемом решении выезжающая боковая панель с переключателем будет только для “мобильного” окна шириной до 540 пикселей. 540 пикселей будет точкой остановки для переключения между интерактивным (для мобильного) и статическим (для десктопа) макетами. В статическом макете sidebar присутствует в виде колонки с навигацией.
CSS-псевдокласс :target
Начнем с переключателя. Для него надо добавить две ссылки <a>
с значениями атрибута href
, соответственно: #sidenav-open
и #
. Для HTML-элемента боковой панели надо добавить атрибут id="sidenav-open"
:
<a href="#sidenav-open"
id="sidenav-button"
title="Открыть меню"
aria-label="Открыть меню"></a>
<a href="#"
id="sidenav-close"
title="Закрыть меню"
aria-label="Закрыть меню"></a>
<aside id="sidenav-open">
…
</aside>
Клик по каждой из этих ссылок изменяет хэш для URL страницы, а затем с помощью CSS-псевдокласса показывается или скрывается боковая панель навигации:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS grid
Раньше для выезжающих боковых панелей было принято использовать контейнеры с абсолютным или фиксированным позиционированием. Однако, с появлением CSS-grid, CSS-свойство grid-area
позволяет назначить несколько элементов в одну строку или столбец.
Сетка
Основной элемент макета #sidenav-container
представляет собой сетку, которая создает 1 строку и 2 столбца с названием stack
для первой.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
Подложка
Анимированный HTML=элемент <aside>
– боковая панель навигации. У него есть 2 дочерних элемента: контейнер с навигацией <nav>
с названием [nav]
и подложка <a>
с названием [escape]
, назначение которой – закрывать панель с меню.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
CSS 3D-преобразования и переходы
Теперь панель навигации на мобильном экране накладывается поверх контента. Для sidebar надо добавить ещё немного CSS. Вот UX-цели, который разберём в следующем разделе:
- Анимировать разворачивание/сворачивание
- Анимировать с плавным движением, только если пользователь не возражает
- Анимировать
visibility
, чтобы фокус клавиатуры не исчезал за пределы экрана
Реализовывать анимацию для движения боковой панели будем с учётом предпочтений пользователя.
Доступность движения
Кто-то не любит плавное движение при появлении боковой панели и предпочитает быстрое появление sidebar. Такое предпочтение можно учитывать с помощью настройки внутри медиа-запроса prefers-reduced-motion: reduce
значения CSS-переменной --duration
. Это учитывает предпочтения пользователя для движения (если доступно), настроенные в операционной системе.
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Теперь, когда боковая панель навигации открывается и закрывается, если пользователь предпочитает ограниченное движение, элемент мгновенно перемещается в поле зрения, без плавного движения.
Transition, transform, translate
Панель скрыта (по умолчанию)
Чтобы установить для боковой панели навигации на мобильном устройстве состояние по умолчанию, за пределами экрана, нужно спозиционировать элемент с помощью transform: translateX(-110vw)
.
Обратите внимание, к типовому значению -100vw
для скрытия за пределы экрана, добавлены ещё 10vw
, чтобы гарантировать, что тень блока боковой навигации не попадёт в видимую область окна, когда панель скрыта.
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
transform: translateX(-110vw);
will-change: transform;
transition:
transform var(--duration) var(--easeOutExpo),
visibility 0s linear var(--duration);
}
}
Панель открыта
Когда, при изменении хэша в URL, :target
соответствует HTML-элементу #sidenav-open
, его надо спозиционировать с помощью translateX()
в установленные для открытого состояния координаты 0
и наблюдать, как CSS переместит элемент из его исходной позиции -110vw
в указанную, равную 0
, в течение var(--duration)
времени.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
Изменение видимости
Следующая цель – реализовать невидимость меню, когда sidebar скрыт, от программ чтения с экрана. Это нужно, чтобы фокус не переводился на меню, скрытое за пределами экрана. Для этого можно изменять CSS-свойство visibility
панели при изменениях :target
:
- Когда панель появляется, чтобы она была видимой и на ней можно было сфокусироваться, не следует изменять свойство
visibility
. - Когда панель скрывается,
visibility
должно изменяться наhidden
, но с задержкой, т.е. после того, как она покинет видимую часть окна
Улучшение UX-доступности
Ссылки
Поскольку рассматриваемое решение полагается на изменение URL-адреса для управления состояниями, здесь следует использовать HTML-элемент <a>
, у которого достаточно полезных для доступности функций. Можно немного украсить интерактивные элементы, чтобы намерения выражались яснее.
<a href="#" id="sidenav-close"
title="Закрыть меню"
aria-label="Закрыть меню"></a>
<a href="#sidenav-open" id="sidenav-button"
class="hamburger"
title="Открыть меню"
aria-label="Открыть меню">
<svg>...</svg>
</a>
Теперь основные кнопки взаимодействия с sidebar четко указывают свое назначение как для мыши, так и для клавиатуры.
:is(:hover, :focus)
Этот удобный функциональный CSS-псевдоселектор позволяет легко использовать стили при наведении и делиться ими с фокусом.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
Немного Javascript
Кнопка Esc для сворачивания
Клавиша Escape
на клавиатуре должна закрывать меню, например так:
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
UX-фокус
Следующий фрагмент помогает сосредоточиться на кнопках открытия и закрытия после того, как они появляются или скрываются.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
Когда боковая панель навигации откроется, фокус переместится на кнопку закрытия. Когда боковая навигация закроется, фокус переместится кнопку открытия. Это делается в JavaScript с помощью вызова для элемента метода focus()
.
Пример выезжающей боковой панели: