一个侧边栏导航组件实现思路

在这篇文章中,我想和大家分享我是如何为 web 原型化一个 Sidenav 组件的,这个组件是响应式的,有状态的,支持键盘导航,可以使用和不使用 Javascript,并且可以跨浏览器工作。

构建一个响应式导航系统是很困难的。有些用户使用键盘,有些用户使用强大的台式机,还有一些用户使用小型移动设备访问。每个访问者都应该能够打开和关闭菜单。

用了哪些技术

在这次组件探索中,我很高兴地结合了一些关键的网络平台特性:

  • 伪类
  • CSS Grid
  • transforms
  • 媒体查询和用户偏好 CSS
  • 用户增强体验

我的解决方案只有一个侧边栏,只有在“移动”视口为540px 或更小时才能切换。540px 将是我们在移动交互式布局和静态桌面布局之间切换的断点。

伪类

一个 <a> 链接将 url 散列设置为 #sidenav-open,另一个设置为 empty('')。最后,一个元素具有匹配散列的 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>

点击这些链接会改变我们网页 URL 的散列状态,然后用一个伪类来显示和隐藏 Sidenav:

@media (max-width: 540px) {
    #sidenav-open {
        visibility: hidden;
    }
    
    #sidenav-open:target {
        visibility: visible;
    }
}

CSS Grid

在过去,我只使用绝对或固定位置 Sidenav 布局和组件。不过,使用网格区域语法,可以为同一行或列分配多个元素。

Stacks

主要的布局元素 #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> 是包含侧边导航的动画元素。它有两个子元素: 导航容器 <nav>命名为 [nav] ,背景幕布 <a> 命名为 [escape],用于关闭菜单。

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

调整 2fr 和 1fr,找到你喜欢的菜单覆盖和负空间关闭按钮的比例。

3D transforms

我们的布局现在是堆叠在一个移动视口大小。除非我添加一些新的样式,否则它将默认覆盖我们的文章。下面是一些我正在努力实现的用户体验:

  • 动画打开和关闭;
  • 只有在用户同意的情况下才使用动画;
  • 键盘焦点不会进入屏幕以外的元素;

当我开始实现动作动画的时候,我想先从可访问性开始。

无障碍运动

不是每个人都想要幻灯片移动的体验。在我们的解决方案中,这个首选项是通过调整媒体查询中的 -- duration CSS 变量来实现的。此媒体查询值表示用户的操作系统对移动的偏好(如果可用)。

#sidenav-open {
    --duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
    #sidenav-open {
        --duration: 1ms;
    }
}

现在,当我们的 sidenav 滑动打开和关闭,如果用户喜欢减少运动,我立即移动元素进入视图,保持没有运动的状态。

Transition, transform, translate

Sidenav 默认是退出状态的。为了将移动设备上 Sidenav 的默认状态设置为屏幕外状态,我将元素的位置设置为:

transform: translateX (- 110vw);

注意,我在典型的屏幕外代码 -100vw 中添加了10vw,以确保当 sidenav 隐藏时,它的盒子阴影不会窥视主视图。

@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() 位置设置为 0。当 URL 哈希值变化的时候,观察到元素会从 -110vw 的位置滑动到 0 的位置。

@media (max-width: 540px) {
    #sidenav-open:target {
        visibility: visible;
        transform: translateX(0);
        transition: transform var(--duration) var(--easeOutExpo);
    }
}

过渡时期的可见性

现在的目标是屏幕阅读器看不到菜单,这样系统就不会把焦点放在屏幕外的菜单上。我通过在: 目标更改时设置可见性转换来实现这一点。

  • 进入时,请勿过渡可见性;立刻可见,因此我可以看到元素滑入并接受焦点。
  • 退出时,给他加一个延迟到过渡效果;

可访问性 UX 增强

链接

此解决方案依赖于更改 URL 以便管理状态。当然,这里应该使用 <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));
}

加上点 JS

键盘上的 Escape 键应该关闭菜单,对吗? 让我们把它实现:

const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
    if (event.code === 'Escape') document.location.hash = '';
});

下一个代码片段帮助我们将注意力集中在打开或关闭按钮上。我想让切换变得简单。

sidenav.addEventListener('transitionend', e => {
    const isOpen = document.location.hash === '#sidenav-open';
    isOpen
        ? document.querySelector('#sidenav-close').focus()
        : document.querySelector('#sidenav-button').focus();
})

当 Sidenav 打开时,集中关闭按钮。当 Sidenav 关闭时,集中打开按钮。我通过在 JS 中的元素上调用 focus() 来实现这一点。

© 版权声明
THE END
喜欢就支持以下吧
点赞1赞赏 分享
吐槽 抢沙发

请登录后发表评论