关于如何构建自适应滑出侧边导航栏的基础概览
在这篇博文中,我想与大家分享我如何设计适用于 Web 的 Sidenav 组件的原型,该组件具有响应式、有状态、支持键盘导航、使用和不使用 JavaScript 均可运行,并且适用于各种浏览器。试用演示版。
如果你更喜欢视频,可以参阅此博文的 YouTube 版本:
概览
构建响应式导航系统并非易事。有些用户使用键盘,有些用户使用功能强大的桌面设备,还有一些用户通过小型移动设备访问。访问的每个人都应该能够打开和关闭菜单。
网络策略
在这项组件探索中,我很高兴能够结合使用一些重要的网络平台功能:
- CSS
:target
- CSS 网格
- CSS transforms
- 针对视口和用户偏好设置的 CSS 媒体查询
- JS for
focus
用户体验增强功能
我的解决方案只有 1 个边栏,并且仅当位于 540px
或更低的“移动”视口时才会切换。540px
将成为我们在移动设备互动布局和静态桌面布局之间切换的断点。
CSS :target
伪类
一个 <a>
链接将网址哈希值设为 #sidenav-open
,另一个链接为空 (''
)。最后,元素的 id
以匹配哈希值:
<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<aside id="sidenav-open">
…
</aside>
点击每个链接都会更改页面网址的哈希状态,然后我使用伪类来显示和隐藏侧边导航栏:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS 网格
过去,我只使用绝对位置或固定位置侧边导航栏布局和组件。不过,网格可以使用其 grid-area
语法将多个元素分配给同一行或列。
堆叠
主要布局元素 #sidenav-container
是一个会创建 1 行 2 列的网格,其中一列的名称为 stack
。当空间受限时,CSS 会将 <main>
元素的所有子项分配给同一个网格名称,将所有元素放入同一空间,从而形成一个堆栈。
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
菜单背景幕
<aside>
是包含侧边导航栏的动画元素。它有两个子项:导航容器 <nav>
(名为 [nav]
)和背景 <a>
(名为 [escape]
),用于关闭菜单。
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
调整 2fr
和 1fr
,为菜单叠加层及其负空间关闭按钮找到您所需的宽高比。
CSS 3D 转换和过渡
现在,我们的布局会按照移动设备视口的大小进行堆叠。在我添加一些新样式之前 它默认会叠加在我们的文章中在下一节中,我要为这部分体验设计一些用户体验:
- 为打开和关闭添加动画效果
- 仅在用户同意的情况下以动画形式呈现动画效果
- 为
visibility
添加动画效果,使键盘焦点不会进入屏幕外元素
在着手实现动作动画时,首先要把无障碍功能放在首位。
易于使用的动作
并非每个人都希望有滑出动作的体验。在我们的解决方案中,通过调整媒体查询中的 --duration
CSS 变量来应用此偏好设置。此媒体查询值表示用户的操作系统对动作的偏好(如果有)。
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
现在,当侧边导航栏滑动打开和关闭时,如果用户更喜欢减少动作,我会立即将元素移入视图,保持没有动作的状态。
转场、变形、翻译
侧边导航栏输出(默认)
为了将移动设备上的侧边导航栏的默认状态设置为屏幕外状态,我使用 transform: translateX(-110vw)
来定位元素。
请注意,我向 -100vw
的典型屏幕外代码添加了另一个 10vw
,以确保侧边导航栏的 box-shadow
在隐藏时不会透过主视口。
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
transform: translateX(-110vw);
will-change: transform;
transition:
transform var(--duration) var(--easeOutExpo),
visibility 0s linear var(--duration);
}
}
位置导航
当 #sidenav
元素与 :target
匹配时,将 translateX()
位置设置为 homebase 0
,并且当网址哈希值发生更改时,CSS 会将该元素从 -110vw
的移出位置滑动到 0
的“in”位置。var(--duration)
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
过渡时的可见性
现在的目标是在菜单出来后隐藏菜单,不让屏幕阅读器看到,这样系统就不会将焦点放在屏幕外的菜单中。为此,我会在 :target
发生变化时设置可见性转换。
- 进入时,不要显示过渡效果;要立即可见,以便我能看到元素滑入并接受焦点。
- 退出时,过渡可见性但会延迟,因此在过渡结束时,过渡可见性会切换为
hidden
。
无障碍用户体验改进
链接
此解决方案依赖于更改网址,以便管理状态。当然,此处应使用 <a>
元素,该元素可以免费获得一些不错的无障碍功能。我们使用能够清晰阐明意图的标签来装饰我们的互动元素。
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
<svg>...</svg>
</a>
现在,我们的主要互动按钮可以清楚地表明其针对鼠标和键盘的意图。
:is(:hover, :focus)
借助这个便捷的 CSS 功能伪选择器,我们还可以共享焦点样式,从而快速包含悬停样式。
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
巧用 JavaScript
按 escape
即可关闭
按键盘上的 Escape
键应能正确关闭菜单?让我们连线。
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
浏览器历史记录
为了防止打开和关闭互动在浏览器历史记录中堆叠多个条目,请将以下内嵌 JavaScript 添加到关闭按钮:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
此操作将在关闭时移除网址历史记录条目,使菜单看起来像从未打开过。
重点打造用户体验
下面的代码段可帮助我们将焦点放在打开按钮和关闭按钮上。我希望切换变得简单。
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
侧边导航栏打开后,聚焦于关闭按钮。侧边导航栏关闭后,聚焦于打开按钮。为此,我会使用 JavaScript 对元素调用 focus()
。
总结
现在你知道我怎么做到的了,你会怎么做?!这样就创建了一些有趣的组件架构! 谁会制作带有槽的第一个版本?🙂
让我们来尝试多样化的方法 并学习在 Web 上构建的所有方法创建一个 Glitch,将您的版本发推给我,然后我将其添加到下面的社区混剪部分。
社区混剪作品
- 使用自定义元素的 @_developit:演示和代码
- 使用 HTML/CSS/JS 的 @mayeedwin1:演示和代码
- @a_nurella 与 Glitch Remix:演示和代码
- 使用 HTML/CSS/JS 的 @EvroMalarkey:演示和代码