Codelab: Создание компонента Sidenav

В этом практическом занятии вы узнаете, как создать адаптивный компонент выдвижной боковой навигации для веб-сайта. Мы будем создавать компонент по ходу дела, начиная с HTML, затем CSS и JavaScript.

Ознакомьтесь с записью моего блога Создание компонента Sidenav, чтобы узнать о функциях веб-платформы CSS, выбранных для создания этого компонента.

Настраивать

  1. Нажмите «Ремикс для редактирования», чтобы сделать проект редактируемым.
  2. Откройте app/index.html .

HTML

Сначала займитесь основами настройки HTML, чтобы было содержимое и несколько полей, с которыми можно работать.

Вставьте следующий HTML-код в тег <body> .

<aside></aside>
<main></main>

Элемент <aside> содержит навигационное меню как дополнительный элемент к элементу <main> , который содержит содержимое основной страницы.

Далее мы заполним эти семантические элементы остальным содержимым страницы.

Добавьте элемент навигации, несколько навигационных ссылок и ссылку закрытия внутри элемента <aside> .

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

Ссылки отлично смотрятся внутри элементов <nav> , а элементы <nav> отлично смотрятся в боковых панелях <aside> . Тем не менее, нам есть над чем работать.

В основном элементе контента добавьте заголовок и статью для семантического хранения содержимого макета.

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

В заголовке есть ссылка для открытия меню. Сбоку — кнопка закрытия. Скоро мы начнём отображать и скрывать элементы в зависимости от размера области просмотра.

В элемент <article> мы вставили предложение-заполнитель. Замените `` своим текстом или вставьте текст, приведённый ниже:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

Именно этот контент и его длина станут причиной прокрутки страницы, когда она превысит высоту области просмотра.

На данный момент вы добавили элемент aside с навигацией, ссылками и способом закрытия боковой навигации. Вы также добавили заголовок, способ открытия боковой навигации и статью к основному элементу. Это уже выглядит аккуратно, семантично и довольно актуально, но мы можем сделать его чище и понятнее для всех. Открытую ссылку в боковой навигации можно было бы обозначить более чётко.

Добавьте атрибуты title и aria-label к элементу ссылки открытия заголовка:

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

Значок открытия SVG также можно было бы обозначить более чётко. Добавьте следующие атрибуты к SVG внутри элемента открытой ссылки:

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

Ссылка «Закрыть» в боковой панели навигации могла бы быть более чётко обозначена. Добавьте атрибуты title и aria-label к элементу ссылки «Закрыть» боковой панели навигации:

<a href="#"></a>
<a href="#" title="Close Menu" aria-label="Close Menu"></a>

CSS

Пора заняться компоновкой элементов. Основной контент и боковая навигация — прямые дочерние элементы тега <body> , так что с них можно начать.

Добавьте следующий CSS-код в css/sidenav.css , чтобы элемент <body> размещал дочерние элементы.

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

По сути, этот макет гласит: создайте именованный stack строк со всем содержимым и 2 столбца в этом ряду, второй из которых также будет именованным stack . Размер первого столбца должен соответствовать минимально необходимому объёму контента, а второй столбец может занимать всё остальное. Затем, если область просмотра ограничена 540px или меньше, поместите элементы боковой навигации и основного контента в одну строку и один столбец, чтобы они располагались друг над другом в сетке 1x1.

Используя эту адаптивную функциональность наложения в качестве основы, мы теперь можем использовать состояние URL-адресной строки для переключения видимости и стиля перехода боковой навигации.

Обновите элемент <aside> обратно в app/index.html :

<aside>
<aside id="sidenav-open">

Это позволяет CSS сопоставлять элемент и хеш URL. Это важно для использования :target . Теперь идентификатор элемента может совпадать с хешем URL, который мы установим с помощью тегов <a> .

Кроме того, для упрощения таргетинга JavaScript добавьте идентификаторы ключевых элементов, управляющих боковой навигацией. Сначала добавьте идентификатор к ссылке открытия боковой навигации:

<a href="#sidenav-open" class="hamburger" title="Open Menu" aria-label="Open Menu">
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">

Затем добавьте идентификатор к ссылке закрытия боковой панели:

<a href="#" title="Close Menu" aria-label="Close Menu"></a>
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

Это завершает создание адаптивной разметки для макроса <body> и связывает нас со строкой URL. Продолжим!

Элемент <aside> тоже имеет аккуратную структуру. У него два дочерних элемента: <nav> — выдвижной компонент, похожий на бумажный лист, и закрывающий элемент <a> , который устанавливает URL-адрес в # . Ссылка не видна справа от выдвижного элемента навигации; это сделано для того, чтобы пользователь мог «кликнуть» по визуальному компоненту, чтобы закрыть его.

Добавьте следующий CSS в css/sidenav.css :

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

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

Далее мне нужно настроить условное наложение основного контента и сохранить позицию при любой прокрутке документа. Это отлично подходит для position: sticky и некоторых overscroll-behavior .

Добавьте следующие стили для боковой навигации:

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

Эти стили гарантируют, что боковая навигация будет соответствовать высоте области просмотра, будет прокручиваться вертикально и содержать прокрутку. Что очень важно, она скрывает элемент. По умолчанию, если область просмотра составляет 540px или меньше, боковая навигация скрывается. Разве что!

Добавьте псевдоселектор :target к элементу #sidenav-open :

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

Если идентификатор этого элемента и адресная строка совпадают, установите для видимости visibility visible . Откройте боковое меню после прокрутки страницы или попробуйте прокрутить страницу, пока открыта боковая навигация. Что вы думаете?

Добавьте следующий CSS-код в конец app/sidenav.css :

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

Эти стили определяют наши кнопки открытия и закрытия, определяют их стили нажатия и прикосновения, а также скрывают их, если размер области просмотра составляет 540px или больше.

Для пущего эффекта давайте добавим CSS-преобразования с уважительной доступностью. Добавьте следующий CSS-код в css/sidenav.css :

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
Демонстрация взаимодействия с длительностью и без нее, примененная на основе медиа-запроса `prefers-reduced-motion`.

Добавьте немного JavaScript

Клавиша Escape должна закрывать меню. Добавьте этот JS-код в js/index.js :

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

Этот код отслеживает событие нажатия клавиши на элементе sidenav. Если это Escape , хеш URL становится пустым, что приводит к выходу sidenav.

Следующий фрагмент UX JS-кода — управление фокусом. Я хочу упростить открытие и закрытие, поэтому жду, пока боковая навигация завершит какой-либо переход, а затем сверяю его с хешем URL, чтобы определить, находится ли она в фокусе или нет. Затем я использую JavaScript, чтобы установить фокус на кнопку, дополняющую ту, которую пользователь только что нажал.

Добавьте следующий JavaScript в js/index.js :

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

Попробуйте это

  • Для предварительного просмотра сайта нажмите «Просмотреть приложение» . Затем нажмите «Полный экран». полноэкранный .

Заключение

Вот и всё, что мне нужно было для этого компонента. Можете смело его дорабатывать, использовать JavaScript-состояние вместо URL и вообще, делать его своим! Всегда есть что добавить и охватить новые варианты использования.

Откройте css/brandnav.css чтобы ознакомиться со стилями, не связанными с макетом, которые я применил к этому компоненту. Я не считал, что это важно для набора функций, на котором я сосредоточился, и надеялся, что разделение стилей и макета будет способствовать копированию и вставке. Возможно, там вы найдете для себя больше полезного!

Как сделать выдвижные адаптивные компоненты боковой навигации? У вас когда-нибудь было больше одного компонента, например, по одному с каждой стороны? Я бы с удовольствием показал ваше решение в видео на YouTube. Обязательно напишите мне в Твиттере или оставьте комментарий с вашим кодом на YouTube — это поможет всем!