Общий обзор того, как создать адаптивный и доступный компонент «хлебных крошек», позволяющий пользователям перемещаться по вашему сайту.
В этом посте я хочу поделиться своими мыслями о том, как создавать компоненты для хлебных крошек. Попробуйте демо-версию .
Если вам удобнее видео, вот версия этого поста на 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 добавлен атрибут aria-hidden="true" . Иконки бесполезны для пользователей, которые видят только контент, поэтому их скрытие от таких пользователей предотвращает добавление лишнего шума.
Split-link .crumb
Здесь происходит различие между традиционной навигационной цепочкой и цепочкой в этом компоненте. Обычно это была бы просто ссылка <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 он находится на видном месте. Один атрибут предоставляет контекст кнопки многим пользователям.

Разделительные украшения
<span class="crumb-separator" aria-hidden="true">→</span>
Разделители необязательны, добавление всего одного тоже отлично работает (см. третий пример в видео выше). Затем я присваиваю каждому aria-hidden="true" поскольку они носят декоративный характер и не требуют оповещения со стороны программы чтения с экрана.
Свойство gap , которое будет рассмотрено далее, упрощает определение расстояния между ними.
Стили
Поскольку цвет использует системные цвета , в основном это пробелы и наложения для стилей!
Направление и поток компоновки

Основной элемент навигации 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> .

.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> .
Заключение
Теперь, когда вы знаете, как я это сделал, как бы вы поступили иначе?! 🙂
Давайте разнообразим наши подходы и изучим все способы создания веб-приложений. Создайте демо-версию, пришлите мне ссылки в Твиттере , и я добавлю её в раздел ремиксов сообщества ниже!
Ремиксы от сообщества
- Tux Solbakk как веб-компонент: демо и код