Skip to content
О сайте Блог Обучение Исследовать узоры Case studies
Содержание
  • Обзор
  • Веб-подходы
    • CSS-псевдокласс :target
    • CSS Grid
    • CSS 3D-преобразования и переходы
    • Улучшения доступности
    • Заключение
  • Ремиксы сообщества
  • Home
  • All articles

Создание компонента sidenav

Фундаментальный обзор того, как создать адаптивный выдвигающийся компонент sidenav

Jan 21, 2021
Available in: English, Español, 日本語 и 한국어
Adam Argyle
Adam Argyle
TwitterGitHubGlitchHomepage
Содержание
  • Обзор
  • Веб-подходы
    • CSS-псевдокласс :target
    • CSS Grid
    • CSS 3D-преобразования и переходы
    • Улучшения доступности
    • Заключение
  • Ремиксы сообщества

В этой статье я хочу поделиться с вами своим способом создания адаптивной боковой панели навигации (sidenav), которая отслеживает состояние, поддерживает управление с клавиатуры, работает с JavaScript и без него и поддерживается разными браузерами. Попробуйте демонстрацию.

Если вы предпочитаете видео, можете посмотреть видеоверсию этой статьи на YouTube:

Обзор #

Создать адаптивную систему навигации непросто. Некоторые пользователи могут работать с помощью клавиатуры, одни при входе на сайт будут использовать мощный компьютер, другие — маленькое мобильное устройство. Но каждый из посетителей должен иметь возможность открыть и закрыть меню.

Демонстрация адаптивности макета на десктопе и мобильных
Светлая и темная тема на iOS и Android

Веб-подходы #

При исследовании этого компонента я совместил несколько важных концепций веб-разработки:

  1. CSS-псевдокласс :target
  2. CSS Grid
  3. CSS-трансформации
  4. CSS-медиазапросы для области просмотра и пользовательских предпочтений
  5. 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 #

Раньше для боковой панели я использовал только макеты и компоненты с абсолютным или фиксированным позиционированием. Технология CSS Grid, однако, с ее синтаксисом grid-area позволяет нам назначать несколько элементов одной строке или столбцу.

Стопки #

Основной элемент макета #sidenav-container представляет собой grid-элемент, который создает 1 строку и 2 столбца, 1 из которых получает имя stack. Когда пространство ограничено, CSS присваивает всем потомкам элемента <main> одно и то же значение grid-области, размещая все элементы в одну и ту же ячейку в виде стопки.

#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-преобразования и переходы #

Теперь наш макет умещается и в размер мобильной области просмотра. Пока я не добавлю несколько новых стилей, боковая панель по умолчанию будет накладываться на нашу статью. Вот функционал, к которому я стремлюсь в следующем разделе:

  • Анимированное открытие и закрытие
  • Анимация только в том случае, если пользователь ее не отключает
  • Анимирование visibility, чтобы фокус клавиатуры не выходил за пределы экрана

Поскольку я приступаю к реализации анимированного движения, в первую очередь давайте начнем с доступности.

Доступная анимация #

Не всем захочется видеть анимацию выдвигающегося меню. В нашем решении предпочтение пользователя применяется путем настройки CSS-переменной --duration внутри медиазапроса. Значение этого медиазапроса представляет предпочтения операционной системы пользователя в отношении анимации (если они доступны).

#sidenav-open {
--duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Демонстрация работы интерфейса с разными настройками длительности анимации

Теперь, когда наша боковая навигация открывается и закрывается, если пользователь предпочитает ограничить анимацию, я мгновенно перемещаю элемент в область просмотра, поддерживая тем временем состояние без движения.

Переход, трансформация, трансляция #

Боковая панель закрыта (по умолчанию) #

Чтобы на мобильных устройствах наша панель боковой навигации по умолчанию находилась за пределами экрана, я позиционирую элемент с помощью 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() в стандартное значение 0 и посмотрите, как CSS при изменении URL-хеша сместит элемент с его исходной позиции -110vw в позицию «открыто», равную 0, в течение времени, установленного в переменной var(--duration).

@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}

Переход свойства visibility #

Теперь, когда панель находится за пределами области просмотра, ее нужно скрыть от программ чтения с экрана, чтобы они не переводили фокус на элементы закадрового меню. Я реализовал это с помощью перехода свойства visibility при смене псевдокласса :target .

  • При открытии применять переход не нужно; сразу видимая панель должна выезжать из-за пределов экрана и получать фокус.
  • При закрытии для свойства visibility нужно применить переход, но с задержкой, чтобы панель стала невидимой (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 позволяет нам задать стили одновременно для состояний hover и focus.

.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-адресов при закрытии панели, как если бы меню никогда не открывалось.

Фокус #

Следующий фрагмент помогает нам поместить фокус на кнопки открытия и закрытия при соответствующем действии панели. Я хочу упростить переключение.

sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';

isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})

Когда боковая панель открывается, фокус попадает на кнопку закрытия. Когда же панель закрывается, фокус попадает на кнопку открытия. Я делаю это с помощью JavaScript, вызывая focus() для элемента.

Заключение #

Теперь вы знаете о моем подходе в реализации этого компонента. Как бы его реализовали вы? Тут есть пространство для творчества. Кто же сделает первую версию со слотами? 🙂

Давайте разнообразим наши подходы и найдем все способы создания компонентов. Создайте демонстрацию на Glitch, напишите мне о своей версии в Твиттере, и я добавлю ее в раздел ремиксов сообщества ниже.

Ремиксы сообщества #

  • @_developit с настраиваемыми элементами: демонстрация и код
  • @mayeedwin1 с HTML/CSS/JS: демонстрация и код
  • @a_nurella с ремиксом на Glitch: демонстрация и код
  • @EvroMalarkey с HTML/CSS/JS: демонстрация и код
CSSDOMJavaScriptКомпоновкаМобильные устройстваUX
Последнее обновление: Jan 21, 2021 — Улучшить статью
Codelabs

See it in action

Learn more and put this guide into action.

  • Codelab: Building a Sidenav component
Return to all articles
Поделиться
подписаться

Contribute

  • Сообщить об ошибке
  • Просмотреть исходный код

Дополнительная информация

  • developer.chrome.com
  • Новости Chrome
  • Разборы конкретных случаев
  • Подкасты
  • Шоу

Соцсети

  • Twitter
  • YouTube
  • Google Developers
  • Chrome
  • Firebase
  • Google Cloud Platform
  • Все продукты
  • Условия и конфиденциальность
  • Правила сообщества

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies.