这项技术探讨了使用:
- 使用CSS的动画
transition和transform - 使用
:focus-within伪类 - 定位的CSS网格
- 动态居中技术
- 下拉菜单的可访问性考虑
如果你曾经为处理 "悬停意图 "的概念而揪心,那么这次升级是为你准备的
在我们走得太远之前,虽然我们的技术100%只使用了CSS,但为了获得更全面的无障碍体验,有必要添加一些Javascript。为了获得最可靠的支持,还需要为一个关键功能添加一个polyfill--:focus-within 。但与需要一个或多个jQuery插件来完成视觉效果的日子相比,我们还是有了很大的进步。
可访问性更新 - 08/18/20: 非常感谢Deque的Michael Fairchild(也是优秀资源a11ysupport.io的创建者)在各种辅助技术上测试了原始解决方案。只用CSS的方法需要一些Javascript来完全满足WCAG 2.1。特别是,需要使用javascript来提供一种非鼠标/非tab的方式来取消菜单(想想转义键)以满足成功标准1.4.13。迈克尔指出了这个WAI-ARIA创作实践演示,它提供了更多关于必要的Javascript功能的信息。这些都是强烈建议为您的最终生产方案增加的内容。
如果您没有使用过Sass,您可能需要花5分钟来了解Sass的嵌套语法,以便最容易理解所给的代码样本。
基础导航HTML
我们将在继续的过程中加强这一点,但这里是我们的起始结构:
<nav aria-label="Main Navigation">
<ul>
<li><a href="#">About</a></li>
<li class="dropdown">
<!-- aria-expanded needs managed with Javascript -->
<button
type="button"
class="dropdown__title"
aria-expanded="false"
aria-controls="sweets-dropdown"
>
Sweets
</button>
<ul class="dropdown__menu" id="sweets-dropdown">
<li><a href="#">Donuts</a></li>
<li><a href="#">Cupcakes</a></li>
<li><a href="#">Chocolate</a></li>
<li><a href="#">Bonbons</a></li>
</ul>
</li>
<li><a href="#">Order</a></li>
</ul>
</nav>
稍微忽略一下button ,这是导航链接的语义标准。这种结构可以灵活地存在于页面的任何地方,因此它可以作为侧边栏的目录,就像它是主导航一样容易。
从一开始,我们就实施了一些专门针对可访问性的功能:
aria-label在 ,以帮助识别它的目的,当辅助技术被用来通过地标导航一个页面时<nav>- 使用
button,作为一个可关注的、可发现的元素来触发下拉菜单的打开。 aria-controls在 ,链接到 的id,将其与辅助技术的菜单联系起来。.dropdown__title.dropdown__menuaria-expandedbutton,在你的最终解决方案中,需要通过Javascript来切换,正如本文开头提到的演示中所指出的那样
正如迈克尔所指出的,使用
button元素也可以让龙语用户说出 "点击按钮 "这样的话来打开菜单。
我们的(大部分)默认起始外观如下:

初始导航样式
首先,我们将给nav 一些容器样式,并将其定义为一个网格容器。然后我们将从nav ul 和nav ul li 中删除默认的列表样式:
nav {
background-color: #eee;
padding: 0 1rem;
display: grid;
place-items: center;
ul {
list-style: none;
margin: 0;
padding: 0;
display: grid;
li {
padding: 0;
}
}
}

我们已经失去了分层定义,但我们可以通过以下方式开始把它带回来:
nav {
// ...existing styles
> ul {
grid-auto-flow: column;
> li {
margin: 0 0.5rem;
}
}
}
通过使用子组合器选择器> ,我们已经定义了顶层的ul ,它是nav 的直接子节点,应该把它的grid-auto-flow 切换到column ,这就有效地更新了它沿x-axis 。然后我们给顶层的li 元素添加边距,以增加定义。现在,未来的下拉项出现在 "Sweets "菜单的下面,并且更明显地成为它的子项。

接下来,我们将首先为所有的链接以及.dropdown__title ,然后只为顶层链接以及.dropdown__title 。这也是我们清除为button 元素继承的本地浏览器风格的地方:
// Clear native browser button styles
.dropdown__title {
background-color: transparent;
border: none;
font-family: inherit;
}
nav {
> ul {
> li {
// All links contained in the li
a,
.dropdown__title {
text-decoration: none;
text-align: center;
display: inline-block;
color: blue;
font-size: 1.125rem;
}
// Only direct links contained in the li
> a,
.dropdown__title {
padding: 1rem 0.5rem;
}
}
}
}

基本的下拉式样式
到目前为止,我们一直依赖元素选择器,但我们将为下拉菜单引入类选择器,因为在一个给定的导航列表中可能有多个。
我们将首先对.dropdown__menu 和它的链接进行样式设计,以便在我们通过定位和动画工作时,帮助更清楚地识别它。
.dropdown {
position: relative;
.dropdown__menu {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0.15em 0.25em rgba(black, 0.25);
padding: 0.5em 0;
min-width: 15ch;
a {
color: #444;
display: block;
padding: 0.5em;
}
}
}

其中一个明显的问题是,.dropdown__menu 影响了nav 容器,你可以从灰色的nav 背景出现在下拉菜单周围看到这一点。
我们可以通过将position: absolute 添加到.dropdown__menu ,使其脱离正常的文档流程来开始解决这个问题。

你可以看到它被排列在父列表项的左边和下面。根据你的设计,这可能是理想的位置。
我们要拿出一个居中的技巧来使菜单在列表项的中央对齐:
.dropdown__menu {
// ... existing styles
position: absolute;
// Pull up to overlap the parent list item very slightly
top: calc(100% - 0.25rem);
// Use the left from absolute position to shift the left side
left: 50%;
// Use translateX to shift the menu 50% of it's width back to the left
transform: translateX(-50%);
}
这个居中技术的神奇之处在于,菜单可以是任何宽度,甚至是动态宽度,它都会适当地居中。

下拉菜单的显示方式
有两个主要的触发器我们希望用来打开菜单::hover 和:focus 。
然而,传统的:focus ,不会持久保持下拉菜单的打开状态。一旦最初的触发器失去焦点,键盘焦点可能仍然在下拉菜单中移动,但从视觉上看,菜单会消失。
:focus-within#
有一个即将到来的伪类叫做:focus-within ,它正是我们所需要的,可以使之成为一个纯CSS的下拉菜单。正如在介绍中提到的,如果你需要支持IE < Edge 79(你需要......目前),它确实需要一个polyfill。
来自MDN,斜体字是为了显示我们要受益的部分。
:focus-withinCSS伪类代表一个已经收到焦点的元素或包含一个已经收到焦点的元素。换句话说,它代表一个本身被:focus伪类匹配的元素*,或者有一个被:focus匹配的后裔。*
按默认值隐藏下拉菜单
在我们揭示下拉菜单之前,我们需要隐藏它,所以我们将使用隐藏样式作为默认状态。
你的第一直觉可能是display: none ,但这将使我们无法优雅地进行动画过渡。
接下来,你可能会尝试简单地使用opacity: 0 ,这可以明显地隐藏它,但会留下 "幽灵链接",因为该元素仍然有计算高度。
相反,我们将使用opacity,transform, 和visibilty 的组合:
.dropdown__menu {
// ... existing styles
transform: rotateX(-90deg) translateX(-50%);
transform-origin: top center;
opacity: 0.3;
}
我们添加不透明度,但不是全部为0,以便以后能有更平滑的效果。
而且,我们更新我们的transform 属性以包括rotateX(-90deg) ,这将使菜单在三维空间中 "向后 "旋转90度。这就有效地消除了高度,并将在显示时产生有趣的过渡。你也会注意到transform-origin 属性,我们添加该属性是为了更新应用变换的点,而不是默认的水平和垂直中心。
此外,为了满足成功标准1.3.2,链接应该从屏幕阅读器用户那里隐藏起来,直到它们被直观地显示出来。我们通过包括visibility: hidden (再次感谢Michael的这个提示!)来确保这种行为。
在我们进行揭示之前,我们需要添加一个transition 属性。我们把它添加到主.dropdown__menu 规则中,以便它在焦点/悬停时和关闭时都适用,也就是 "向前 "和 "向后"。
.dropdown__menu {
// ... existing styles
transition: 280ms all ease-out;
}
揭示下拉的内容
有了之前的所有设置,在悬停和聚焦时显示下拉菜单可以简明地完成:
.dropdown {
// ... existing styles
&:hover,
&:focus-within {
.dropdown__menu {
opacity: 1;
transform: rotateX(0) translateX(-50%);
visibility: visible;
}
}
}
首先,我们将visibilty (否则其他属性将不起作用),然后我们将rotateX 重置为0,然后将opacity 一路提升到1 ,以便完全可见。
这就是结果:

rotateX 属性允许菜单从后面摆动的外观,而opacity 只是使其整体过渡得更柔和。
再次说明,为了实现完全无障碍,需要Javascript完全处理键盘辅助技术事件,这些事件并不总是触发
:focus。这意味着一些有视力的键盘用户可能会发现下拉链接,但如果没有发出:focus事件,他们就不会看到下拉菜单真正打开。查看w3c演示,了解如何在这个解决方案中完成对Javascript的整合。
处理悬停意向
如果你在网络方面已经有一段时间了,我希望下面的内容会让你觉得🤯。
当我第一次开始与下拉菜单作斗争时,我主要是为IE7创建它们。在一个大项目中,几个团队成员问了一些问题:"如果我只是在菜单上滚动/鼠标,你能不能阻止菜单出现?"。在经过大量的Google搜索(包括尝试用正确的短语来获得我想要的东西)后,我终于找到了解决方案,就是hoverIntent jQuery插件。
我需要设置这个,因为我们使用的是transition 属性,我们还可以添加一个非常小的延迟。对于一般的目的,这将防止下拉动画触发 "开车 "的鼠标移动。
当我们在一行中定义所有的过渡属性时,顺序很重要,顺序中的第二个数值将被选为延迟值:
.dropdown__menu {
// ... existing styles
transition: 280ms all 120ms ease-out;
}
请看结果:

它需要一个相当悠闲的翻转来触发菜单,我们可以粗略地推断出它的意图是要打开菜单。这个延迟仍然很短,在打开菜单之前不会被有意识地注意到,所以这是一个胜利
你仍然可以选择使用Javascript来加强这一点,特别是如果它要启动一个 "巨型菜单",那就更有破坏性了,但这仍然是相当令人高兴的。
下拉菜单指示器
悬停的意图是一回事,但实际上我们需要一个额外的提示,让用户知道这个菜单有额外的选项。一个非常常见的惯例是模仿本地选择元素的指示器的 "圆点 "或 "向下箭头"。
为了添加这个,我们将更新.dropdown__title 样式。我们将把它定义为一个inline-flex 容器,然后创建一个:after 元素,使用边框技巧来创建一个向下的箭头。我们使用一个破折号translateY() ,使其与我们的文本光学对齐。
.dropdown {
// ... existing styles
.dropdown__title {
display: inline-flex;
align-items: center;
&:after {
content: "";
border: 0.35rem solid transparent;
border-top-color: rgba(blue, 0.45);
margin-left: 0.25em;
transform: translateY(0.15em);
}
}
}

在手机上关闭菜单
这里是另一个地方,最终你可能要用Javascript来增强。
为了保持它只适用于CSS,并为非应用型网站所接受,你需要在主体上应用tabindex="-1" ,有效地允许在菜单之外的任何点击来移除焦点,并允许它关闭。
这有点牵强--而且可能会让用户有点沮丧--所以你可能想用Javascript来增强它在滚动时的隐藏功能,特别是如果你定义nav ,使用position: sticky ,并与用户一起滚动。