Codelab:构建 Sidenav 组件

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

请参阅我的博文构建 Sidenav 组件,了解为构建此组件而选择的 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:

<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>

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

到目前为止,您已经添加了一个 aside 元素,其中包含导航、链接和关闭侧边导航栏的方式。 您还添加了标题、打开侧边导航栏的方式以及主元素中的文章。 这已经很简洁、语义化且相当经典了,但我们可以让它对所有人来说更简洁明了。侧边栏中的打开链接可以标记得更清楚。

向标题打开链接元素添加属性 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 或更小时,隐藏该侧边栏。除非!

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

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

当该元素的 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();
});

试试看

  • 如需预览网站,请按查看应用。然后按全屏图标 全屏

总结

以上就是我对组件的需求。您可以随意在此基础上进行构建,使用 JavaScript 状态(而非网址)来驱动它,总之,让它成为您自己的!总会有更多内容需要添加或更多使用情形需要涵盖。

打开 css/brandnav.css,查看我为此组件应用的相关非布局样式。我认为它对当时我关注的功能集并不重要,并且希望将样式与布局分离能够鼓励复制和粘贴。您可以在那里学到更多知识!

如何制作滑出式自适应侧边导航栏组件? 您是否曾有过多个,例如两侧各有一个?我很乐意在 YouTube 视频中展示您的解决方案,请务必在 Twitter 上提及我或在 YouTube 中评论您的代码,这会对大家有所帮助!