以下是 Ray Messina 的客座文章。Ray 希望能分享此技巧,以此回馈他过去从本网站学到的知识,这真是太棒了。您可能已经了解 jQuery 的 .has
方法,该方法允许您在某个元素具有另一个选择器作为后代时选择该元素。遗憾的是,CSS 中还没有这样的选择器。但是,如果您对 HTML 结构有所了解,则可以使用位置选择器的组合来模拟它。Ray 将对此进行解释。
最近,我正在开发一个下拉导航栏,并希望将包含子菜单的菜单项与不包含子菜单的菜单项区分开来。我希望能够自动执行此操作,而无需依赖 JavaScript 或向标记添加类。我只是希望它能够像该项目知道自己是否有子菜单一样工作。
导航提示的必要性
您可能知道,菜单是链接列表,因此,将其标记为 <ul>
是标准做法。扩展开来,下拉菜单仅仅是嵌套的 <ul>
。下拉菜单是现代(以及不太现代)网页设计的常见组件。使用纯 CSS,可以以任何方式设置导航菜单上层的样式,并隐藏子级,以便仅在访问者将鼠标悬停在相应区域时显示它们。
许多设计人员对此感到满意。但是,从 UX/UI 的角度来看,这是有欠缺的,因为它让用户必须浏览整个菜单才能找到哪些部分包含其他导航链接。这意味着访问者要么浪费时间查找并为此感到沮丧,要么完全错过您网站的部分区域!这完全不好。
解决此问题的常用方法是,简单地向包含 <ul>
(子菜单)的 <li>
添加一个类,以便我们可以将这些项目的样式与不包含子导航的项目的样式区分开来。非常简单,但也非常乏味且不那么优雅。
另一种方法(假设上层项目未编码为链接)是使用标签之间的差异作为杠杆。也就是说,创建伪链接:设置 <li>
的直接子元素 <span>
的样式,以指示后面还有更多链接,并对比设置锚标签的样式,使其不具有其他子项。如果您的下拉菜单很简单,并且您可以专门以这种方式对其进行结构化,则此方法非常方便。它更简洁,因为您不必向标记添加“submenu”类,但是,与之前一样,它仍然要求您手动构建列表内容,或者您的 CMS 能够预测哪些列表项将包含其他 <ul>
作为子菜单。
自动执行!
我曾遇到过其他自动设置包含其他列表的列表项样式的方法,但它们使用了绝对定位和子 <ul>
的伪元素。虽然很巧妙,但 CSS 计算有时可能会很棘手,或者根据您用来布局菜单和/或定位子菜单的其他技术或您希望实现的整体效果,该方法可能完全无法实现。
更好的方法
理想情况下,如果有一个 CSS 选择器可以让我们查询某个元素是否包含另一种元素作为直接子元素,类似于 jQuery 的 .has()
方法,那就太好了。
我们可以使用 li a:first-child:nth-last-child(x) { }
实现几乎相同的功能。
关键是要有预期的子元素数量(HTML 元素计划生育?)。很可能会有两个元素:锚点和 <ul>
,尽管可以调整此技术以适用于任何数量或子元素,只要您具有规则模式。
这是一个快速示例。标记只是您标准的嵌套 UL,但请注意,我仅在 HTML 中使用了一个类,在根 <ul>
上。自己试一下,在任何级别添加任意数量的嵌套列表!

创建下拉菜单演示
让我们对这个想法进行测试!
HTML:保持简洁
<nav>
<ul class="nav">
<li><a href="#">About</a></li>
<li><a href="#">Portfolio</a>
<ul>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
</ul>
</li>
<li><a href="#">Resume</a>
<ul>
<li><a href="#">item a lonng submenu</a></li>
<li><a href="#">item</a>
<ul>
<li><a href="#">Ray</a></li>
<li><a href="#">Veronica</a></li>
<li><a href="#">Bushy</a></li>
<li><a href="#">Havoc</a></li>
</ul>
</li>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
</ul>
</li>
<li><a href="#">Download</a></li>
<li><a href="#">Rants</a>
<ul>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
<li><a href="#">item</a></li>
</ul>
</li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
CSS
一些通用的样式来提升美观度
nav {
display: block;
text-align: center;
}
nav ul {
margin: 0;
padding:0;
list-style: none;
}
.nav a {
display:block;
background: #111;
color: #fff;
text-decoration: none;
padding: 0.8em 1.8em;
text-transform: uppercase;
font-size: 80%;
letter-spacing: 2px;
text-shadow: 0 -1px 0 #000;
position: relative;
}
.nav{
vertical-align: top;
display: inline-block;
box-shadow:
1px -1px -1px 1px #000,
-1px 1px -1px 1px #fff,
0 0 6px 3px #fff;
border-radius:6px;
}
.nav li {
position: relative;
}
.nav > li {
float: left;
border-bottom: 4px #aaa solid;
margin-right: 1px;
}
.nav > li > a {
margin-bottom: 1px;
box-shadow: inset 0 2em .33em -0.5em #555;
}
.nav > li:hover,
.nav > li:hover > a {
border-bottom-color: orange;
}
.nav li:hover > a {
color:orange;
}
.nav > li:first-child {
border-radius: 4px 0 0 4px;
}
.nav > li:first-child > a {
border-radius: 4px 0 0 0;
}
.nav > li:last-child {
border-radius: 0 0 4px 0;
margin-right: 0;
}
.nav > li:last-child > a {
border-radius: 0 4px 0 0;
}
.nav li li a {
margin-top: 1px;
}
然后魔法就发生了
.nav li a:first-child:nth-last-child(2):before {
content: "";
position: absolute;
height: 0;
width: 0;
border: 5px solid transparent;
top: 50% ;
right:5px;
}
这基本上是此技术的核心成分。在此示例中,我使用了锚元素的 :before
伪元素来绘制箭头。伪元素不是该技术的必要条件。我也可以轻松地更改锚点本身的背景;一旦您定位了元素,就可以使其执行您想要的几乎任何操作。
最后,为了完善它,一些定位和箭头样式 CSS 代码。
/* submenu positioning*/
.nav ul {
position: absolute;
white-space: nowrap;
border-bottom: 5px solid orange;
z-index: 1;
left: -99999em;
}
.nav > li:hover > ul {
left: auto;
margin-top: 5px;
min-width: 100%;
}
.nav > li li:hover > ul {
left: 100%;
margin-left: 1px;
top: -1px;
}
/* arrow hover styling */
.nav > li > a:first-child:nth-last-child(2):before {
border-top-color: #aaa;
}
.nav > li:hover > a:first-child:nth-last-child(2):before {
border: 5px solid transparent;
border-bottom-color: orange;
margin-top:-5px
}
.nav li li > a:first-child:nth-last-child(2):before {
border-left-color: #aaa;
margin-top: -5px
}
.nav li li:hover > a:first-child:nth-last-child(2):before {
border: 5px solid transparent;
border-right-color: orange;
right: 10px;
}
以下是在 CodePen 上运行的完整示例
正如您可能猜到的,您还可以使用其他选择器/选择器组合,例如 :only-child
、:first-child:last-child
、:first-child:not(:last-child)
等。但我发现 :nth-child(x):nth-last-child(x)
**提供了最大的灵活性**,并用作内置回退(因为它允许我们直接定位元素,而不是通过排除),并且使用其他选择器/选择器组合不会获得太大的跨浏览器支持优势。
就是这样。简单、优雅且完全自动;它应该就是这样。除了 IE 之外,几乎所有浏览器都支持此功能,IE 只能在 IE9+ 中提供支持。在撰写本文时,这些选择器的估计全球支持率约为 87%,这还不错。
不错,谢谢你的分享
在 FF 25 Beta 中不起作用,当我移动光标时(无论哪个方向),下拉菜单都会立即消失
我的结果
临时添加此 CSS,以查看问题所在并找到解决方案
享受!
在我看来,这是糟糕的代码。对于未来的维护者来说,它很难阅读,而且性能也不佳。更好的解决方案是更改标记并向各个元素添加一些类,从而使 HTML 和 CSS 相互分离。但是,我理解并非每个人都有这种便利,在这种情况下,此解决方案可能还不错。
我同意。
尽管我非常喜欢 first-child 和 nth-child,但我经常发现我会重构(如果可以的话)使用实际的类来提高可读性。我相信,使代码更容易被参与项目的其他人理解,将带来更大的益处。
我只在快速原型设计布局或无法轻松访问 HTML 的环境中使用伪类。
我可能是目前最大的标记纯粹主义者,多年来我一直像疯子一样滥用 nth-child,但最终我开始改变主意了。类被滥用(垂直分隔符代码),nth-child 也被滥用(如上所述),但我们需要一些简洁易懂的中间地带作为默认选择,我认为丑陋的 OOCSS 不是答案。
巧妙,巧妙……我觉得如果有一个“父级”选择器,一切都会变得简单得多。
我真的很喜欢我们在导航到子菜单时箭头旋转 180 度的方式。很棒且聪明的作品。
一个巧妙的解决方案……但它是否过度自动化了……我敢打赌,大多数 CMS 都可以配置为在包含子导航元素的父导航元素上输出一个类……从而节省大量复杂的 CSS 技巧……
此外,在“li”元素上使用悬停不如在“a”元素上使用悬停那样易于访问……因为你可以在“a”元素上设置一个与“悬停”状态匹配的“焦点”状态……这对在网站上使用 Tab 键浏览的用户很有帮助……如果我错了,请更正我,但你无法将焦点设置到“li”元素上……
导航元素应该可以通过鼠标和键盘进行导航……现在也必须考虑触摸操作……
当我读到“另一种方法,假设上层项目未编码为链接,就是利用标签的这种差异。也就是说,创建伪链接……”时,我正是这样想的。
我之前也这样做过,认为自己做了一个良好的 SEO 实践,即
<a href="">
应该用于“真实”链接,因为它们具有非常好的 SEO 权重,后来才意识到我完全破坏了主导航项目的可访问性,因为使用键盘的访问者现在无法 Tab 键切换到不是<a href="">
的元素。现在我知道永远不要再这样做。
正确,你无法聚焦非“a”元素——最近有一项大工作需要处理这个问题。
两件事
-这在最新的 Chrome 中不起作用。
简历项的子菜单会弹出,但当你尝试将鼠标悬停在其上时会消失。
-从代码角度来看,这不太优雅。
你能录制一个屏幕截图展示它“不起作用”的方式吗?我也听到另一个人抱怨过这个问题,但一直无法复现。我也运行着最新的 Chrome。
这是一个技巧。当元素不单独存在时,这是一种巧妙的选择元素的方法。对于它达不到你的优雅标准,我感到抱歉。以我的经验,理解这样的想法在日常 CSS 技巧中很有用。
将鼠标非常缓慢地从上向下移动。我个人没有在 Chrome 中测试过,但在 Firefox 中,它在某个点会消失。至少在 Firefox 中,这是由于 CSS 中的“边距”值造成的,换句话说,你最终会将鼠标悬停在一个不属于菜单的空白区域上(:hover 状态不再保持)。在 Firefox 中,如果你足够快地向下滑动鼠标,浏览器就没有足够的时间使其完全消失,因此 :hover 状态得以保持,并且没有问题。
这种问题在 IE6 时代非常令人烦恼,但现在很少发生了。但它仍然可能发生,这里就是一个例子。删除有问题的边距,你就不会遇到问题。
我会让 Ryan 详细说明他遇到的 Chrome 问题。
你可以考虑使用代码来突出显示它具有子元素的事实,并使用其他方法来实现下拉菜单的显示/隐藏选项,我想……
糟糕!我对最新的 Chrome 的说法是错误的,事实证明我的 Chrome 更新程序实际上有故障 :(
我已经重新安装了 Chrome,现在可以正常工作了。
关于优雅,我理解 CSS 技巧……只是感觉如果我把这个传递给其他人,他们不理解这个技巧,可能很难弄清楚这段 CSS 在做什么,也许一些 CSS 注释就足够了 :)
另一种选择是设置
<ul>
的样式以指示此元素中存在下拉菜单。默认情况下,你隐藏下拉
<li>
,但不会隐藏<ul>
本身。当它(或其父级)未被悬停时,<ul>
具有指示下拉元素的样式,当它被悬停时,下拉菜单会正常显示。这是一种完成下拉菜单(我们知道它存在可用性问题)的非常有趣的方法,但我们却无法完全摆脱它,就像我们的老朋友 IE ><。
如果有人必须构建下拉菜单并且不能使用 JavaScript,那么这是一个很棒的救星。我之所以这么说,是因为几个月前我不得不改造一个具有响应式设计的网站,并且不允许我在小屏幕设备的菜单中使用 JavaScript。感谢 Aaron Gustafson :)
请注意,对于我来说,在 Firefox 24.0 中,下拉菜单在 5 次尝试悬停中失败了 3 次。这肯定是一个小定位问题,但它们确实在此刻我打字的时候失败了。
此外,我不认为在包含下拉菜单的
<li>
中分配类有什么问题,如果你想保持原始标记的整洁,或者由于任何原因无法访问标记,你可以使用 JavaScript 添加这些类。我们所有人一直都在使用 JavaScript 来创建下拉菜单(不一定是菜单),我个人认为我不会很快使用纯 CSS 下拉菜单,但这篇文章肯定是我遇到此类棘手情况时必备的教程。
我真的很喜欢箭头上下切换的方式,非常具有描述性和视觉上的帮助。
这是一个非常巧妙的技巧,但在这种情况下添加一个类并不是那么乏味。你用凌乱的 CSS 牺牲了干净的标记。
如今,我发现构建网站是在保持标记整洁和保持 CSS 整洁之间进行平衡。我越来越发现,我更喜欢通过在标记中使用一些额外的元素/类来保持 CSS 的整洁。
呃,伙计们……“Targeting” 只拼写两个“T”。
非常感谢。
我得加入“为什么不使用类”的行列。
当然,这是一个巧妙且很棒的 CSS,但实际上,很多未来的维护人员不会完全理解它。
哇。这引起了争议。
我想我有点“老派”了。这个概念是关于在不更改 HTML 的情况下用其他内容标记项目;这种技术也可能用于菜单之外的情况。我遇到过一些人,他们只能访问或了解 CSS,而不是实际的 CMS 模板;在这种情况下,这可能是一个救星,例如。CSS3 选择器确实提供了大量控制元素的选项,我发现它很有用(一如既往,取决于具体情况)。
我自己经常使用“选择加入”类,但我喜欢 CSS “响应”标记的想法,尤其是在我看到诸如:
<li class='menu sub long pink current other item item-15'>...</li>
之类的东西时。在这个特定演示的审美方面,它解决了“死区”问题
.nav > li li:hover > ul {
/* 将此规则替换为以下规则 */
border-left: 1px solid #fff;
}
我正在我的 code pen 上更新它。
好主意,Ray,这是一个有趣的技巧。
添加类是否更容易是一个有效的问题,但就 CSS 而言,该技术是有效的、信息丰富的和具有指导意义的,据我所知,它以前从未用于这种情况。
看起来是这样的?;)
http://cdpn.io/edrHC
好的,我就直接切入主题了。:-) 这里有没有人愿意简单解释一下开发者是如何让这个页面上的导航栏 https://www.att.com/olam/passthroughAction.myworld?actionType=Manage 按照这种方式运作的,作为回报,我将来会请他们喝一两杯他们喜欢的饮料?
我特别想复制水平子导航栏的运作方式。我喜欢每个子导航栏在鼠标悬停时激活,并且一直显示直到你将鼠标悬停到另一个列表项上,然后那个列表项的子导航栏出现并一直显示。例如,当你将鼠标悬停在橙色菜单中的“Shop”上时,它会打开包含“Wireless”、“Bundles”、“Digital TV”等的子导航栏。然后,当你将鼠标悬停在“myAT&T”上时,它会显示“Overview”、“Bill & Payments”等项目。这种用户体验很好,因为它减少了用户导航到不同部分所需的点击次数。我正在尝试在我的应用程序中复制这种效果,以此来减少用户(有些使用 IE8)的重复操作。
即使只是简要概述,无需所有细节,比如你是否认为它主要由 jQuery 驱动,我也会非常感谢!提前感谢!
可以把它想象成 4 个菜单。它们有主菜单(Shop、My ATT、Support)。然后,它们为每个主菜单项都有一个菜单(因此有 Shop 菜单、My ATT 菜单和 Support 菜单)。这些菜单使用 CSS 隐藏。然后,它们使用 jQuery 向应该可见的菜单添加一个类。该类随后会取消隐藏该菜单。
谢谢!
CSS 做了所有操作。
太棒了!
我还有另一种方法,就是使用以下 jQuery 代码:
$(‘.nav’).find(‘li’).has(‘ul’).addClass(‘parent’);
然后,你可以使用
:after
或:before
在其上添加一些内容或以不同的方式进行自定义。真的吗?每个人都在抱怨一行 CSS 代码的可维护性?
对比
甚至
我在这里见过更疯狂的事情完全没有受到质疑。
带宽节约怎么了?特别是对于大型菜单,此技巧可以删除大量“不必要”的字符。
这是一个非常公平的观点。我也见过更疯狂的代码被通用设计社区接受,甚至没有提出异议。我赞扬这种技术。它利用了我们工具箱中的现有工具。我读过一些关于获取父选择器的评论——这是会发生的事情吗?在某些方面,这将是一件好事——例如在这个示例中——在其他方面,如果使用不当,它可能会很麻烦。
我添加了一个项目的链接,它用新页面替换了当前导航栏。如何才能在同一导航栏下打开链接?
它在 IE 8 上运行正常,有什么解决方法吗?
如何实现活动菜单?
非常简洁干净。我想知道是否有人将其改编为 WordPress 菜单?
非常感谢