构建侧边导航栏组件

关于如何构建自适应滑出侧边导航栏的基础概览

在这篇博文中,我想和大家分享一下我是如何设计 Sidenav 的 Web 组件原型的, 响应迅速、有状态、支持键盘导航、可使用或不使用 JavaScript, 并可跨浏览器运行。试用演示版

如果你更喜欢视频,可以参阅此博文的 YouTube 版本:

概览

构建响应式导航系统并非易事。部分用户将使用键盘 有些网站配备强大的桌面设备,还有一些公司会通过小型移动设备访问网站。 访问的每个人都应该能够打开和关闭菜单。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
从桌面设备到移动设备的自适应布局演示
<ph type="x-smartling-placeholder">
调低 iOS 和 Android 设备上的浅色主题和深色主题

网络策略

在这项组件探索中,我很高兴能够将几项重要的 Web 平台功能结合在一起:

  1. CSS :target
  2. CSS 网格
  3. CSS 转换
  4. 针对视口和用户偏好设置的 CSS 媒体查询
  5. JS for focus 用户体验增强功能

我的解决方案只有 1 个边栏,并且只在“移动设备”上切换视口大小不超过 540px540px 将成为我们在移动设备互动布局和静态桌面布局之间切换的断点。

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 使用 grid-area 语法, 我们可以将多个元素分配到同一行或同一列。

主要布局元素 #sidenav-container 是一个会创建 1 行 2 列的网格, 1 个名称为 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> 是包含侧边导航栏的动画元素。它具有 2 个子元素:名为 [nav] 的导航容器 <nav> 和一个背景 <a> 名为 [escape],用于关闭菜单。

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

调整2fr和使用 1fr 查找菜单叠加层及其负空间关闭按钮所需的宽高比。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
更改比例后会发生什么情况的演示。

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 的移出位置滑动到“in” 网址哈希值发生更改时 0 相对于 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 把你的版本发到 Twitter 上,然后我会将其添加到 下方的社区混剪部分。

社区混剪作品