在导航栏中使用子菜单定位菜单元素

Avatar of Ray Messina
Ray Messina

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是 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 上运行的完整示例

查看 CodePen 上 Ray Messina (@RayM) 编写的笔 标记子菜单项(性感版本)

正如您可能猜到的,您还可以使用其他选择器/选择器组合,例如 :only-child:first-child:last-child:first-child:not(:last-child) 等。但我发现 :nth-child(x):nth-last-child(x) **提供了最大的灵活性**,并用作内置回退(因为它允许我们直接定位元素,而不是通过排除),并且使用其他选择器/选择器组合不会获得太大的跨浏览器支持优势。

就是这样。简单、优雅且完全自动;它应该就是这样。除了 IE 之外,几乎所有浏览器都支持此功能,IE 只能在 IE9+ 中提供支持。在撰写本文时,这些选择器的估计全球支持率约为 87%,这还不错。