Базовый обзор того, как создать адаптивную выдвижную боковую навигацию
В этой публикации я хочу рассказать вам, как я создал прототип компонента Sidenav для веб-сайта, который отличается адаптивностью, отслеживанием состояния, поддерживает навигацию с помощью клавиатуры, работает как с JavaScript, так и без него, а также работает в различных браузерах. Попробуйте демо-версию .
Если вы предпочитаете видео, вот версия этого поста на YouTube:
Обзор
Создать адаптивную систему навигации непросто. Некоторые пользователи будут использовать клавиатуру, у других — мощные компьютеры, а некоторые будут заходить на сайт с небольшого мобильного устройства. Каждый посетитель должен иметь возможность открывать и закрывать меню.
Веб-тактики
В этом исследовании компонентов мне посчастливилось объединить несколько важнейших функций веб-платформы:
- CSS
:target
- CSS- сетка
- CSS -преобразования
- CSS Media Requests для области просмотра и пользовательских предпочтений
- JS для улучшения UX-
focus
В моем решении предусмотрена одна боковая панель, которая переключается только при разрешении «мобильной» области просмотра 540px
или меньше. 540px
будет нашей точкой останова для переключения между интерактивным макетом для мобильного устройства и статическим макетом для настольного компьютера.
CSS :target
псевдокласс
Одна ссылка <a>
устанавливает хеш URL на #sidenav-open
, а другая — на пустой ( ''
). Наконец, элементу присваивается id
, соответствующий хешу:
<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<aside id="sidenav-open">
…
</aside>
Нажатие каждой из этих ссылок изменяет хеш-состояние URL нашей страницы, затем с помощью псевдокласса я показываю и скрываю боковую навигацию:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS-сетка
Раньше я использовал только макеты и компоненты боковой навигации с абсолютным или фиксированным позиционированием. Однако Grid, благодаря синтаксису grid-area
, позволяет назначать несколько элементов одной строке или столбцу.
Стеки
Основной элемент макета #sidenav-container
— это сетка, которая создаёт одну строку и два столбца, каждый из которых называется stack
. При ограниченном пространстве CSS присваивает всем дочерним элементам <main>
одно и то же имя сетки, размещая все элементы в одном пространстве, создавая стек.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
Фон меню
Элемент <aside>
— это анимированный элемент, содержащий боковую навигацию. Он имеет два дочерних элемента: контейнер навигации <nav>
с именем [nav]
и фон <a>
с именем [escape]
, который используется для закрытия меню.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
Отрегулируйте 2fr
и 1fr
чтобы найти желаемое соотношение для наложения меню и кнопки закрытия с отрицательным пространством.
CSS 3D-преобразования и переходы
Наш макет теперь сгруппирован по размеру области просмотра мобильного устройства. Пока я не добавлю новые стили, он по умолчанию будет накладываться поверх статьи. Вот пример UX, к которому я стремлюсь в следующем разделе:
- Анимация открытия и закрытия
- Используйте анимацию с движением только в том случае, если пользователь с этим согласен.
- Анимировать
visibility
, чтобы фокус клавиатуры не попадал на закадровый элемент
Приступая к реализации анимации движения, я хочу в первую очередь думать о доступности.
Доступное движение
Не всем нужен эффект выдвижения. В нашем решении эта настройка применяется путём настройки CSS-переменной --duration
в медиазапросе. Это значение медиазапроса отражает настройки операционной системы пользователя для движения (если они доступны).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Теперь, когда наша боковая навигация открывается и закрывается, если пользователь предпочитает меньше движений, я мгновенно перемещаю элемент в поле зрения, сохраняя состояние без движения.
Переход, преобразование, перевод
Sidenav out (по умолчанию)
Чтобы установить состояние по умолчанию для нашей боковой навигации на мобильном устройстве на внеэкранное состояние, я размещаю элемент с помощью transform: translateX(-110vw)
.
Обратите внимание, я добавил еще 10vw
к типичному внеэкранному коду -100vw
, чтобы гарантировать, что box-shadow
не будет заглядывать в основную область просмотра, когда она скрыта.
@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);
}
}
Sidenav в
Если элемент #sidenav
совпадает с :target
, установите позицию translateX()
на homebase 0
и наблюдайте, как CSS перемещает элемент из его внешней позиции -110vw
в его внутреннюю позицию 0
с помощью var(--duration)
при изменении хеша URL.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
Видимость перехода
Теперь моя цель — скрыть меню от скринридеров, когда оно открыто, чтобы системы не переводили фокус на закадровое меню. Я добился этого, установив переход видимости при изменении :target
.
- При входе не меняйте видимость; будьте видимы сразу, чтобы я мог видеть, как элемент появляется и принимает фокус.
- При выходе из объекта сделайте его видимым, но задержите его, чтобы в конце выхода он стал
hidden
.
Улучшения пользовательского опыта доступности
Ссылки
Это решение основано на изменении URL для управления состоянием. Естественно, здесь следует использовать элемент <a>
, который бесплатно предоставляет некоторые полезные функции доступности. Давайте украсим наши интерактивные элементы метками, чётко выражающими их назначение.
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
<svg>...</svg>
</a>
Теперь наши основные кнопки взаимодействия четко обозначают свое предназначение как для мыши, так и для клавиатуры.
:is(:hover, :focus)
Этот удобный функциональный псевдоселектор CSS позволяет нам быстро адаптировать стили наведения курсора, также разделяя их с фокусом.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
Добавьте JavaScript
Нажмите escape
, чтобы закрыть
Клавиша Escape
на клавиатуре должна закрывать меню, верно? Давайте подключим её.
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
История браузера
Чтобы предотвратить накопление нескольких записей в истории браузера при открытии и закрытии, добавьте следующий код JavaScript к кнопке закрытия:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
Это приведет к удалению записи истории URL-адресов при закрытии, и все будет выглядеть так, как будто меню никогда не открывалось.
Фокус UX
Следующий фрагмент кода помогает нам сфокусироваться на кнопках открытия и закрытия после их открытия или закрытия. Я хочу сделать переключение между ними простым.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
Когда боковая навигация открывается, фокусируем кнопку закрытия. Когда боковая навигация закрывается, фокусируем кнопку открытия. Я делаю это, вызывая focus()
для элемента в JavaScript.
Заключение
Теперь, когда вы знаете, как я это сделал, как бы поступили вы?! Получается интересная архитектура компонентов! Кто сделает первую версию со слотами? 🙂
Давайте разнообразим наши подходы и изучим все способы разработки в интернете. Создайте Glitch , отправьте мне твит своей версии, и я добавлю её в раздел «Ремиксы сообщества» ниже.
Ремиксы сообщества
- @_developit с пользовательскими элементами: демо и код
- @mayeedwin1 с HTML/CSS/JS: демо и код
- @a_nurella с Glitch Remix: демо и код
- @EvroMalarkey с HTML/CSS/JS: демо и код