本课程简要介绍了如何构建响应式且易于访问的面包屑导航组件,以便用户浏览您的网站。
Adam Argyle
在本文中,我想分享一下构建面包屑导航组件的方法。试用演示版。
演示
如果您更喜欢视频,请观看此帖子的 YouTube 版本:
概览
面包屑导航组件会显示用户在网站层次结构中的位置。这个名称源自《汉泽尔和格雷特》的故事:汉泽尔和格雷特在黑暗的树林中行走时,在后面撒下面包屑,然后沿着面包屑的方向回到了家。
本文中的面包屑导航不是标准面包屑导航,而是类似于面包屑导航。它们通过使用
背景用户体验
在上面的组件演示视频中,占位符类别是视频游戏类型。此轨迹是通过浏览以下路径创建的:home »
rpg » indie » on sale,如下所示。
此面包屑导航组件应让用户能够浏览此信息层次结构,快速准确地跳转分支并选择页面。
信息架构
我发现,从集合和项的角度考虑会很有帮助。
集合
集合是可供选择的选项数组。在本帖子面包屑导航原型版的首页中,集合包括第一人称射击游戏、角色扮演游戏、格斗游戏、地下城探索游戏、体育游戏和益智游戏。
项目
视频游戏是一种项,特定合集也可以是项,前提是它代表另一个合集。例如,“RPG”既是项,也是有效的集合。如果是商品,则表示用户位于相应合集页面上。例如,它们位于“角色扮演游戏”页面上,该页面会显示角色扮演游戏列表,包括额外的子类别“AAA”“独立游戏”和“自行发布”。
用计算机科学术语来说,此面包屑导航组件表示一个多维数数组:
const rawBreadcrumbData = {
"FPS": {...},
"RPG": {
"AAA": {...},
"indie": {
"new": {...},
"on sale": {...},
"under 5": {...},
},
"self published": {...},
},
"brawler": {...},
"dungeon crawler": {...},
"sports": {...},
"puzzle": {...},
}
您的应用或网站将具有自定义信息架构 (IA),用于创建不同的多维数数组,但我希望集合着陆页和层次结构遍历的概念也能体现在您的面包屑导航中。
布局
Markup
优质组件始于适当的 HTML。在下一部分中,我将介绍我的标记选择以及它们对整个组件的影响。
深色和浅色方案
上面代码段中的 color-scheme 元标记会告知浏览器,此网页需要使用浅色和深色浏览器样式。示例面包屑导航不包含任何这些配色方案的 CSS,因此面包屑导航将使用浏览器提供的默认颜色。
注意: 请尝试从演示页中移除所有样式。由于标记结构良好,该组件无需这些属性即可正常运行。
导航元素
适合使用
图标
当某个图标在页面上重复出现时,SVG
如需使用此方法,请向页面添加一个隐藏的 SVG 元素,并将图标封装在具有唯一 ID 的
浏览器会读取 SVG HTML,将图标信息放入内存,然后继续处理页面的其余部分,并引用该 ID 以便进一步使用该图标,如下所示:
只需定义一次,即可使用多次,同时对网页性能的影响降至最低,并可灵活设置样式。请注意,aria-hidden="true" 已添加到 SVG 元素中。这些图标对仅听内容的浏览者而言没有用处,因此向这些用户隐藏这些图标可避免不必要的干扰。
分屏链接 .crumb
传统面包屑导航条与此组件中的面包屑导航条在此有所不同。通常,这只是一个 链接,但我添加了伪装的选择项来实现遍历体验。.crumb 类负责布局链接和图标,而 .crumbicon 负责将图标和选择元素堆叠在一起。我将其称为分屏链接,因为它的功能与分屏按钮非常相似,但用于页面导航。
链接和一些选项并无特别之处,但可以为简单的面包屑导航添加更多功能。向
分隔符装饰
分隔符是可选的,只添加一个分隔符也非常有效(请参阅上方视频中的第三个示例)。然后,我为每个 aria-hidden="true" 都设置了 ARIA 无障碍功能,因为它们是装饰性元素,不需要屏幕阅读器读出。
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 图标与“几乎不可见”的
.crumbicon {
--crumbicon-size: 3ch;
display: grid;
grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
place-items: center;
& > * {
grid-area: stack;
}
}
.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
首先,无论您在网站或应用中使用哪种路由器,当用户更改面包屑导航时,都需要更新网址并向用户显示相应的页面。其次,为了规范用户体验,请确保在用户仅浏览
由 JavaScript 处理的两项关键用户体验措施: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
})
})
实现此策略的方法是,监控每个
总结
现在您已经知道我是如何解决的,您会怎么做?
🙂?
让我们多元化我们的方法,了解在 Web 上构建的所有方式。
制作一个演示版,在推特上向我发送链接,我会将其添加到下方的社区混剪部分!
社区混剪作品
将 Tux Solbakk 用作 Web 组件:演示和代码