Базовый обзор того, как создать адаптивную слайд-сайд-панель.
В этом посте я хочу поделиться с вами тем, как я создал прототип веб-компонента Sidenav, который отзывчив, сохраняет состояние, поддерживает навигацию с помощью клавиатуры, работает с JavaScript и без него и работает во всех браузерах. Попробуйте демо .
Если вы предпочитаете видео, вот версия этого поста на YouTube:
Обзор
Трудно создать отзывчивую навигационную систему. Некоторые пользователи будут работать с клавиатурой, у некоторых будут мощные настольные компьютеры, а некоторые будут посещать сайт с небольшого мобильного устройства. Каждый посетитель должен иметь возможность открывать и закрывать меню.
Веб-тактика
В этом исследовании компонентов мне посчастливилось объединить несколько важных функций веб-платформы:
- CSS
:target
- CSS- сетка
- CSS- преобразования
- CSS-медиа-запросы для области просмотра и предпочтений пользователя
- JS для улучшения
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
— это сетка, которая создает 1 строку и 2 столбца, по одному из которых называются 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>
— это анимационный элемент, содержащий боковую навигацию. У него есть 2 дочерних элемента: навигационный контейнер <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 по умолчанию на мобильном устройстве в состояние «за кадром», я позиционирую элемент с помощью 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
соответствует :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.
Заключение
Теперь, когда вы знаете, как я это сделал, как бы вы поступили?! Это создает забавную архитектуру компонентов! Кто будет делать 1-ю версию со слотами? 🙂
Давайте разнообразим наши подходы и изучим все способы разработки в Интернете. Создайте глюк , напишите мне в Твиттере свою версию, и я добавлю ее в раздел ремиксов сообщества ниже.
Ремиксы сообщества
- @_developit с пользовательскими элементами: демо и код
- @mayeedwin1 с HTML/CSS/JS: демо и код
- @a_nurella с ремиксом Glitch: демо и код
- @EvroMalarkey с HTML/CSS/JS: демо и код