Создание компонента хлебных крошек

Общий обзор того, как создать адаптивный и доступный компонент «хлебных крошек», позволяющий пользователям перемещаться по вашему сайту.

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

Демо

Если вам удобнее видео, вот версия этого поста на YouTube:

Обзор

Компонент «хлебные крошки» показывает, где пользователь находится в иерархии сайта. Название происходит от сказки о Гензеле и Гретель , которые оставили хлебные крошки позади себя в темном лесу и смогли найти дорогу домой, проследив за крошками в обратном направлении.

Хлебные крошки в этом посте — это не стандартные хлебные крошки , а скорее похожие на них. Они предоставляют дополнительную функциональность, размещая соседние страницы непосредственно в навигации с помощью тега <select> , что делает возможным многоуровневый доступ.

Фоновый UX

В приведенном выше демонстрационном видеоролике компоненты представляют собой категории-заполнители, обозначающие жанры видеоигр. Эта цепочка создается путем перехода по следующему пути: home » rpg » indie » on sale , как показано ниже.

Этот компонент "хлебные крошки" должен позволять пользователям перемещаться по этой информационной иерархии, перескакивая между ветками и выбирая страницы быстро и точно.

Информационная архитектура

Мне кажется, полезно мыслить категориями коллекций и предметов.

Коллекции

Коллекция — это набор вариантов на выбор. На главной странице прототипа навигационной цепочки этого поста представлены следующие коллекции: FPS, RPG, файтинг, dungeon crawler, спорт и головоломка.

Предметы

Видеоигра — это элемент, и конкретная коллекция также может быть элементом, если она представляет другую коллекцию. Например, RPG — это элемент и допустимая коллекция. Когда это элемент, пользователь находится на странице этой коллекции. Например, он находится на странице RPG, где отображается список ролевых игр, включая дополнительные подкатегории AAA, Indie и Self Published.

В терминах информатики этот компонент "хлебные крошки" представляет собой многомерный массив :

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

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

Макеты

Разметка

Хорошие компоненты начинаются с соответствующего HTML-кода. В следующем разделе я расскажу о выбранных мной вариантах разметки и о том, как они влияют на компонент в целом.

Темная и светлая цветовая гамма

<meta name="color-scheme" content="dark light">

Метатег color-scheme в приведенном выше фрагменте кода сообщает браузеру, что для этой страницы требуются светлый и темный стили оформления. В примерах хлебных крошек нет CSS-кода для этих цветовых схем, поэтому в хлебных крошках будут использоваться цвета по умолчанию, предоставляемые браузером.

<nav class="breadcrumbs" role="navigation"></nav>

Для навигации по сайту целесообразно использовать элемент <nav> , который имеет неявную роль ARIA «навигация» . В ходе тестирования я заметил, что наличие атрибута role изменяет способ взаимодействия программы чтения с экрана с элементом: он фактически объявлялся как «навигация», поэтому я решил добавить его.

Иконки

Когда иконка повторяется на странице, элемент SVG <use> позволяет задать path один раз и использовать его для всех экземпляров иконки. Это предотвращает повторение одной и той же информации о пути, что приводит к увеличению размера документа и потенциальной несогласованности путей.

Для использования этого метода добавьте на страницу скрытый SVG-элемент и оберните значки в элемент <symbol> с уникальным идентификатором:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

Браузер считывает HTML-код SVG, помещает информацию об иконке в память и продолжает работу с остальной частью страницы, используя этот ID для дальнейшего использования иконки, например, так:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

Инструменты разработчика, демонстрирующие отрисованный элемент использования SVG.

Определите один раз, используйте сколько угодно раз, с минимальным влиянием на производительность страницы и гибким стилем. Обратите внимание, что к элементу SVG добавлен атрибут aria-hidden="true" . Иконки бесполезны для пользователей, которые видят только контент, поэтому их скрытие от таких пользователей предотвращает добавление лишнего шума.

Здесь происходит различие между традиционной навигационной цепочкой и цепочкой в ​​этом компоненте. Обычно это была бы просто ссылка <a> , но я добавил пользовательский интерфейс для обхода страницы с помощью замаскированного выпадающего списка. Класс .crumb отвечает за размещение ссылки и значка, а класс .crumbicon — за объединение значка и элемента выпадающего списка. Я назвал это разделенной ссылкой, потому что её функции очень похожи на функции разделенной кнопки , но для навигации по странице.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

Ссылка и несколько опций — это ничего особенного, но они добавляют функциональность к простой навигационной цепочке. Добавление title к элементу <select> полезно для пользователей программ чтения с экрана, предоставляя им информацию о действии кнопки. Однако это оказывает одинаковую помощь и всем остальным, вы увидите, что на iPad он находится на видном месте. Один атрибут предоставляет контекст кнопки многим пользователям.

Скриншот, на котором невидимый элемент select находится в состоянии наведения курсора, и отображается его контекстная всплывающая подсказка.

Разделительные украшения

<span class="crumb-separator" aria-hidden="true">→</span>

Разделители необязательны, добавление всего одного тоже отлично работает (см. третий пример в видео выше). Затем я присваиваю каждому aria-hidden="true" поскольку они носят декоративный характер и не требуют оповещения со стороны программы чтения с экрана.

Свойство gap , которое будет рассмотрено далее, упрощает определение расстояния между ними.

Стили

Поскольку цвет использует системные цвета , в основном это пробелы и наложения для стилей!

Направление и поток компоновки

В инструментах разработчика отображается выравнивание навигационной цепочки с помощью наложения flexbox.

Основной элемент навигации nav.breadcrumbs задает пользовательское свойство с ограниченной областью видимости, доступное для использования дочерними элементами, и в остальном устанавливает горизонтальное вертикальное выравнивание макета. Это гарантирует выравнивание элементов навигации, разделителей и значков.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Одна из навигационных цепочек отображается вертикально, с наложениями flexbox.

Каждый элемент .crumb также устанавливает горизонтальное вертикальное выравнивание с некоторым отступом, но специально нацелен на дочерние элементы ссылок и задает стиль white-space: nowrap . Это крайне важно для многословных хлебных крошек, поскольку мы не хотим, чтобы они занимали несколько строк. Позже в этой статье мы добавим стили для обработки горизонтального переполнения, вызванного этим свойством white-space .

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

Добавлен атрибут aria-current="page" , чтобы ссылка на текущую страницу выделялась среди остальных. Это не только обеспечит пользователям программ чтения с экрана четкий индикатор того, что ссылка ведет на текущую страницу, но и визуально оформит элемент таким образом, чтобы пользователи с нормальным зрением получили аналогичный опыт.

Компонент .crumbicon использует сетку для размещения SVG-иконки поверх "почти невидимого" элемента <select> .

В инструментах разработчика Grid показано наложение кнопки, где строка и столбец имеют имя stack.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

Элемент <select> находится последним в DOM, поэтому он расположен на вершине стека и является интерактивным. Добавьте стиль opacity: .01 , чтобы элемент оставался доступным, и в результате получится выпадающий список, идеально соответствующий форме иконки. Это хороший способ настроить внешний вид элемента <select> , сохранив при этом его встроенную функциональность.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

Переполнение

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

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

Стили переполнения обеспечивают следующий пользовательский интерфейс:

  • Горизонтальная прокрутка с ограничением нахождения вне зоны прокрутки.
  • Горизонтальный отступ для прокрутки.
  • Точка привязки находится на последнем крошке. Это означает, что при загрузке страницы первая крошка загружается, фиксируется и отображается.
  • Удаляет функцию привязки точки из Safari, которая плохо справляется с сочетанием горизонтальной прокрутки и эффекта привязки.

Запросы СМИ

Для небольших экранов можно внести небольшую корректировку, скрыв надпись «Главная», оставив только иконку:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

Сравниваем панировочные сухари с этикеткой «собственный бренд» и без нее.

Доступность

Движение

В этом компоненте не так уж много движения, но, обернув переход проверкой на prefers-reduced-motion , мы можем предотвратить нежелательное движение.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

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

JavaScript

Во-первых, независимо от типа маршрутизатора, используемого на вашем сайте или в приложении, при изменении пользователем навигационной цепочки URL-адрес должен быть обновлен, и пользователю должна быть показана соответствующая страница. Во-вторых, для стандартизации пользовательского опыта убедитесь, что не происходит неожиданных переходов при простом просмотре опций <select> .

Два важных момента, влияющих на пользовательский опыт и требующих обработки с помощью JavaScript: изменение элемента в выпадающем списке и предотвращение срабатывания события изменения элемента в немедленном режиме <select> ).

Предотвращение немедленных событий необходимо из-за использования элемента <select> . В Windows Edge, и, вероятно, в других браузерах, событие changed элемента select срабатывает, когда пользователь выбирает параметры с помощью клавиатуры. Именно поэтому я назвал это немедленным событием, поскольку пользователь лишь псевдовыбрал параметр, как при наведении курсора или фокусировке, но еще не подтвердил свой выбор нажатием enter или click . Немедленное событие делает функцию изменения категории компонента недоступной, поскольку открытие выпадающего списка и простой выбор элемента вызовет событие и изменит страницу до того, как пользователь будет готов.

Более качественное событие изменения <select>

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

Стратегия заключается в отслеживании событий нажатия клавиш на каждом элементе <select> и определении, была ли нажата клавиша подтверждения навигации ( Tab или Enter ) или пространственной навигации ( ArrowUp или ArrowDown ). На основе этого определения компонент может решить, ждать ли или переходить к следующему шагу, когда сработает событие для элемента <select> .

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы поступили иначе?! 🙂

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

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