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:

<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 图标。在 open link 元素内为 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 列,第二行也命名为 stack。第一列应根据其最低内容需求调整大小,第二列可填满其余列。然后,如果处于 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> link 元素(将网址设置为 #)。该链接在纸张滑出导航栏的右侧不可见,因此用户可以“点击”可视组件将其关闭。

将以下 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,它会将网址哈希设为空,使侧边导航栏过渡出。

用户体验 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,查看我应用于此组件的非布局相关样式。我认为它对我关注的功能集不重要,并且希望将样式与布局分离有助于促进复制和粘贴。您还能从中学到更多知识!

如何制作滑出式自适应侧边导航栏组件? 您是否遇到过不止 1 个(例如两面都一个)的情况?我很乐意在 YouTube 视频中推介您的解决方案,请务必发推文给我,或在 YouTube 上用您的代码发表评论,这将对大家大有帮助!