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

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

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

Демо

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

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

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

Фоновый UX

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

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

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

Я считаю, что полезно думать о коллекциях и предметах.

Коллекции

Коллекция представляет собой массив вариантов на выбор. На домашней странице прототипа хлебных крошек этого поста представлены коллекции FPS, RPG, Brawler, Dungeon Crawler, Sports и Puzzle.

Предметы

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

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

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

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

Макеты

Разметка

Хорошие компоненты начинаются с соответствующего 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>

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

<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>

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

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

Здесь традиционная «хлебная крошка» и те, что входят в этот компонент, расходятся. Обычно это будет только ссылка <a> , но я добавил UX обхода с замаскированным выбором. Класс .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 он находится спереди и в центре. Один атрибут предоставляет контекст кнопки многим пользователям.

Снимок экрана с наведенным невидимым элементом выбора и отображением его контекстной подсказки.

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

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

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

Свойство gap , о котором речь пойдет далее, упрощает их размещение.

Стили

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

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

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

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

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

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

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

Каждый .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 DevTools показан поверх кнопки, где строка и столбец имеют имя 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;
  }}
}

Стили переполнения настраивают следующий UX:

  • Горизонтальная прокрутка с ограничением чрезмерной прокрутки.
  • Горизонтальная прокрутка.
  • Одна точка защелкивания на последней крошке. Это означает, что при загрузке страницы первые крошки загружаются и видны.
  • Удаляет точку привязки из 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 срабатывает, когда пользователь просматривает параметры с помощью клавиатуры. Вот почему я назвал это нетерпеливым, поскольку пользователь только псевдовыбрал опцию, например, наведение курсора или фокус, но не подтвердил выбор с помощью 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> .

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы‽ 🙂

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

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