Базовый обзор того, как создать адаптивный и доступный компонент хлебных крошек, позволяющий пользователям перемещаться по вашему сайту.
В этом посте я хочу поделиться мыслями о том, как создавать компоненты хлебных крошек. Попробуйте демо .
Если вы предпочитаете видео, вот версия этого поста на 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>
Определите один раз, используйте столько раз, сколько захотите, с минимальным влиянием на производительность страницы и гибким стилем. Обратите внимание, что к элементу SVG добавлено aria-hidden="true"
. Значки бесполезны для тех, кто просматривает сайт и слышит только контент. Скрытие их от этих пользователей не позволяет им создавать ненужный шум.
Разделенная ссылка .crumb
Здесь традиционная «хлебная крошка» и те, что входят в этот компонент, расходятся. Обычно это будет только ссылка <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
, о котором речь пойдет далее, упрощает их размещение.
Стили
Поскольку в цвете используются системные цвета , то в основном это пробелы и стопки для стилей!
Направление и поток макета
Основной элемент навигации 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;
}}
}
Стили переполнения настраивают следующий 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>
.
Заключение
Теперь, когда вы знаете, как я это сделал, как бы вы‽ 🙂
Давайте разнообразим наши подходы и изучим все способы разработки в Интернете. Создайте демо, пришлите мне ссылку в Твиттере , и я добавлю ее в раздел ремиксов сообщества ниже!
Ремиксы сообщества
- Tux Solbakk как веб-компонент: демо и код