Codelab:构建 Sidenav 组件

在此 Codelab 中,您将学习如何在 Web 上构建自适应滑出式侧边导航布局组件。我们将逐步构建该组件,首先是 HTML,然后是 CSS,最后是 JavaScript。

请参阅我的博文构建侧边栏组件,了解为构建此组件而选择的 CSS Web 平台功能。

设置

  1. 点击 Remix to Edit 即可修改项目。
  2. 打开 app/index.html

HTML

首先,了解 HTML 设置的基本知识,以便有内容和一些框可用。

将以下 HTML 代码放入 <body> 标记中。

<aside></aside>
<main></main>

<aside> 用于包含导航菜单,作为 <main>(用于包含主要页面内容)的补充元素。

接下来,我们将使用网页的其余内容填充这些语义元素。

<aside> 元素中添加一个导航元素、一些导航链接和一个关闭链接。

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

链接非常适合在 <nav> 元素中使用,而 <nav> 元素非常适合在 <aside> 边栏中使用。不过,我们仍有许多改进空间。

在主要内容元素中,添加标题和文章以语义上容纳布局内容。

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

标题中包含菜单打开链接。边栏中包含关闭按钮。我们很快就会根据视口大小显示和隐藏元素。

<article> 元素中,我们粘贴了一个占位符句子。将“”替换为您自己的内容,或粘贴下面提供的 Lorem ipsum:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

当此类内容及其长度超出视口高度时,会导致网页可滚动。

到目前为止,您已添加了侧边栏元素,其中包含导航栏、链接和关闭侧边栏的方法。 您还在主要元素中添加了标题、用于打开侧边栏的方法和一篇文章。 这已经很简洁、清晰且不易过时了,但我们可以让它变得更加简洁明了,让所有人都能看懂。边栏中的打开链接可以更明确地标记出来。

向标题打开链接元素添加 titlearia-label 属性:

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

打开的 SVG 图标也可以更明确地标记。 将以下属性添加到打开的链接元素内的 SVG:

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

边栏中的关闭链接可以标记得更清晰。将属性 titlearia-label 添加到侧边栏关闭链接元素:

<a href="#"></a>
<a href="#" title="Close Menu" aria-label="Close Menu"></a>

CSS

现在来排列元素。主要内容和侧边栏是 <body> 标记的直接子元素,因此从这里入手是个不错的选择。

将以下 CSS 添加到 css/sidenav.css 中,以便 <body> 元素排列子元素。

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

此布局的本质是:创建一个包含所有内容且名为 stack 的行,并在该行中创建 2 个列,其中第 2 个列也名为 stack。第 1 列的大小应根据其最小内容需求确定,第 2 列可占用其余空间。然后,如果在限制为 540px 或更小的视口中,请将侧边导航栏和主要内容元素放入同一行和列中,使其在 1x1 网格中彼此堆叠。

以此响应式堆叠功能为基础,我们现在可以利用网址栏的状态来切换侧边栏的可见性和转换样式。

app/index.html 中重新更新 <aside> 元素:

<aside>
<aside id="sidenav-open">

这样,CSS 便可将元素与网址哈希一起匹配。这对于 :target 的使用至关重要。现在,该元素的 ID 可以与我们将使用 <a> 标记设置的网址哈希匹配。

此外,为了更轻松地定位 JavaScript,请为控制侧边栏的主要元素添加 ID。首先,为侧边栏打开链接添加 ID:

<a href="#sidenav-open" class="hamburger" title="Open Menu" aria-label="Open Menu">
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">

接下来,为侧边栏关闭链接添加 ID:

<a href="#" title="Close Menu" aria-label="Close Menu"></a>
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

至此,宏 <body> 响应式堆叠布局已完成,并且我们已与网址栏相关联。 让我们继续构建吧!

<aside> 的布局也很整洁。它有 2 个子元素,一个是 <nav>,即滑出的纸张式组件,另一个是用于将网址设置为 # 的闭合 <a> 链接元素。该链接位于纸质侧边滑动导航栏右侧,不可见;这样用户就可以“点击关闭”视觉组件来关闭它。

将以下 CSS 添加到 css/sidenav.css

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

我认为比例和名称非常出色,网格在这里可以发挥出色作用,并为设计师提供大量控制功能。

接下来,我需要有条件地叠加主内容,并在文档滚动时保留我的位置。这对于 position: sticky 和某些 overscroll-behavior 来说非常有用。

为侧边栏添加以下样式:

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

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

这些样式可确保侧边栏的高度与视口高度相同,并会垂直滚动并包含滚动条。非常重要的是,它会隐藏元素。默认情况下,当视口小于或等于 540px 时,系统会隐藏该侧边栏。除非!

:target 伪类选择器添加到 #sidenav-open 元素:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

如果该元素的 ID 与网址栏的 ID 相同,请将 visibility 设置为 visible。滚动页面后,请继续打开侧边菜单,或尝试在侧边菜单处于打开状态时滚动页面。您有何看法?

将以下 CSS 添加到 app/sidenav.css 的底部:

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

这些样式定位到我们的“打开”和“关闭”按钮,指定了它们的点按和触摸样式,并且在视口大小等于或大于 540px 时会隐藏它们。

为了增添一些亮点,我们来添加一些兼顾无障碍功能的 CSS 转换。 将以下 CSS 添加到 css/sidenav.css

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
基于 `prefers-reduced-motion` 媒体查询,演示了应用和未应用时长的互动效果。

添加一些 JavaScript

Escape 键应会关闭菜单。将以下 JS 添加到 js/index.js

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

这会监听侧边栏元素上的按键事件。如果为 Escape,则会将网址哈希设置为空,使侧边栏转换为非活动状态。

接下来介绍的 UX JS 部分是焦点管理。为了简化打开和关闭操作,我会等到侧边栏完成某种转换,然后与网址哈希进行交叉检查,以确定它是处于打开还是关闭状态。然后,我使用 JavaScript 将焦点设置为与用户刚刚按下的按钮相辅相成。

将以下 JavaScript 添加到 js/index.js

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

试试看

  • 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏

总结

以上就是我对该组件的需求总结。您可以随意在此基础上进行构建,使用 JavaScript 状态(而非网址)驱动它,并根据自己的需求进行使用!我们还会不断添加更多内容或涵盖更多用例。

打开 css/brandnav.css 查看我应用于此组件的非布局相关样式。我认为这对我关注的功能集来说并不重要,并且希望将样式与布局分离,以鼓励用户复制和粘贴。您可以从中学习到更多知识!

如何制作滑出式自适应侧边栏组件? 您是否有过 1 个以上的耳鸣,例如两侧各有一个?我很乐意在 YouTube 视频中展示您的解决方案,请务必在推特上向我发推文或在 YouTube 中发表评论并附上代码,这对大家来说都很有帮助!