构建带有嵌套下拉菜单的纯 CSS 菜单
好了,我们的任务是使用 <ul> 元素构建一个带有嵌套列表的下拉菜单,我们可以根据需要嵌套任意数量的项目,并且它们的行为就像一个下拉列表结构。那就开始吧。
以下是代码笔的结果:
由于下拉菜单水平扩展,它无法完全响应。而且它使用悬停作为触发器,这在主板上肯定是个问题。你可以更改它的扩展方式,或者把它改成汉堡包侧边栏之类的,那是另一篇文章的主题了。总之,这段代码仍然节省了大量的 JavaScript 代码。
目前,我们将重点介绍如何构建下拉列表结构。阅读本文,你只需要具备 HTML 和 CSS/SCSS 的基础知识。
初始结构
我们的初始 html 结构将用于创建一个导航栏来保存我们的菜单,并且必须是这样的:
<div class="menu">
<ul>
<li class="link">
<a href="">This is a link</a>
</li>
<li>
This will open a dropdown soon
</li>
</ul>
</div>
您可以在任何地方添加下拉菜单。虽然不应该这么做,但您可以这样做。这里,我们将在导航栏上添加下拉菜单。因此,“menu”div 将固定在页面顶部。您也可以使用 <nav> 标签。以下是将 div 固定在顶部的 CSS。
.menu {
// define the height of the menu
--menu-height: 40px;
// holder and ul general style
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100vw;
}
如果您不熟悉 css vars,可以阅读这篇文章:
https://dev.to/sarah_chima/an-introduction-to-css-variables-cmj
--menu-height 是一个 CSS 变量,用于定义导航栏的高度。它还可以用来定义导航栏的子元素位置。
现在我们有了固定导航,重要的是要注意,当瞄准其中的 ul 时,我们有三个范围:
-
我们可以定位所有 ul(整个范围)。对于整个范围,您只需使用“.menu ul”作为选择器即可。
-
我们只能定位第一个 ul(内部作用域)。它是菜单容器,也是菜单的第一级。您可以使用“.menu > ul”来选择它,我们稍后会详细讨论。
-
我们可以只定位嵌套的 ul(嵌套范围)。这就是我们的下拉菜单。您可以使用“.menu > ul li ul”来选择它们,这将选择列表项内的所有 ul,顺便说一下,这些 ul 将被放置在固定 div 的第一个 ul(菜单容器)内。
因此,首先我们将针对所有 ul,以便为它们添加共同的外观,避免代码重复。
请注意,我们也对 <a> 标签进行了样式设置,这将规范所有 li 的外观,即使它们是链接或下拉菜单。
.menu {
// ...
ul {
list-style: none;
padding: 16px;
margin: 0;
li, li a {
opacity: .8;
color: #ffffff;
cursor: pointer;
transition: 200ms;
text-decoration: none;
white-space: nowrap;
font-weight: 700;
&:hover {
opacity: 1;
}
a {
display: flex;
align-items: center;
height: 100%;
width: 100%;
}
}
}
}
现在,菜单内的所有 ul 和 li 标签都将继承上述样式。我们还需要一个箭头来标记菜单上带有下拉菜单的项目。
但在 CSS 术语中,我们如何知道哪些项目有下拉菜单,哪些没有?
我们简单地添加一个类来区分不同的 li。当我们只有一个 <li> 时,它是一个下拉菜单。当我们有一个带有“.link”类的 <li> 时,它是一个链接。
知道了这一点,我们可以像这样将箭头添加到 li 中:
.menu {
// ...
ul {
// ...
// lets put an arrow down
// to the li`s with dropdown
li {
padding-right: 36px;
&::before {
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFA500;
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
}
.link {
// links dont need arrow
&::before {
padding-right: 0;
display: none;
}
}
}
}
要了解我们如何仅使用 css 创建箭头,您可以阅读 css-tricks 上的这篇文章:https://css-tricks.com/snippets/css/css-triangle/。
菜单
“.menu” div 中的第一个 <ul> 元素将用作菜单容器。我们希望它呈现为一个水平列表,其中包含多个菜单项,可以是链接,也可以是下拉菜单。因此,我们需要将列表项并排放置,并隐藏其子元素:
.menu {
// ...
// the first ul inside the container
// is the menu, so must be visible
// and have its own style
> ul {
display: flex;
height: var(--menu-height);
align-items: center;
background-color: #000000;
// the first ul elements can be a
// link or an li with a nested ul.
// the nested ul will be a dropdown
li {
position: relative;
margin: 0 8px;
// the dropdown style
ul {
// THE DROPDOWN GOES HERE
}
}
}
}
我们必须使用 > 运算符来选中菜单 div 中的第一个 ul 元素。我们需要它来使第一个 ul 看起来像一个菜单,而不会与内部 ul 混淆。
请注意代码示例中的注释“// THE DROPDOWN GOES HERE”。如果您查看我们的 HTML 结构,会发现列表项必须包含一个 ul(下拉菜单)。此注释准确地显示了我们在 CSS 代码中将此下拉菜单定位到的位置。
但首先,让我们将其添加到 html 中
<div class="menu">
<ul>
<li class="link">
<a href="">This is a link</a>
</li>
<li>
Now we have a dropdown
<ul>
<li class="link">
<a href="">Sub A</a>
</li>
<li class="link">
<a href="">Sub B</a>
</li>
</ul>
</li>
</ul>
</div>
下拉菜单
下拉菜单是菜单容器中列表项内的每个 <ul> 元素,也就是“.menu”div 上的第一个 ul。我们必须将它传递给 CSS。但首先,让我们了解一下 CSS 是如何处理它的:
// the menu
.menu {
// the menu holder with options
> ul {
// the menu items
li {
// the item dropdown
ul {
}
}
}
}
li 元素必须在鼠标悬停时显示其 <ul> 子元素。因此,让我们告诉列表项如何显示。但在此之前,我们必须告诉下拉菜单它们应该如何显示以及显示在哪里:
.menu {
// ...
> ul {
// ...
li {
// the holder
position: relative;
margin: 0 8px;
ul {
// the dropdown
visibility: hidden;
opacity: 0;
padding: 0;
min-width: 160px;
background-color: #333;
position: absolute;
top: calc(var(--menu-height) + 5px);
left: 50%;
transform: translateX(-50%);
transition: 200ms;
transition-delay: 200ms;
// the dropdown items style
li {
margin: 0;
padding: 8px 16px;
display: flex;
align-items: center;
justify-content: flex-start;
height: 30px;
padding-right: 40px;
// lets put an arrow right
// to the inner li`s with
// dropdowns
&::before {
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 5px solid #FFA500;
}
// every dropdown after the
// first must open to the right
ul {
top: -2%;
left: 100%;
transform: translate(0)
}
&:hover {
background-color: #000000;
}
}
}
// on hover an li (not an <a>)
// must show its ul (dropdown)
&:hover {
> ul {
opacity: 1;
visibility: visible;
transition-delay: 0ms;
}
}
}
}
}
这是一个很长且重要的部分。让我们来理解一下。
我们要选中列表项中的所有 ul,并将其转换为下拉菜单。要实现此目的,我们必须遵循以下步骤:
- 开始告诉 CSS 如何绘制嵌套的 ul 以及将其放置在何处。起初,这个 ul 是不可见的,当我们将鼠标悬停在其父元素上时才会显示出来。当 ul 可见时,以下代码将负责将所有元素放置在正确的位置:
// ...
position: absolute;
top: calc(var(--menu-height) + 5px);
left: 50%;
transform: translateX(-50%);
transition: 200ms;
transition-delay: 200ms;
顶部将下拉菜单设置为位于导航栏之后,这就是我们设置--menu-height var 的原因。
左键 + transform 会使下拉菜单相对于其父级居中。transition 会使它变得平滑,delay 会使光标在下拉菜单之间导航有间隔。
-
包含下拉菜单的列表项 (ul) 必须是相对的。因此,我们可以为下拉菜单赋予绝对位置,并使其相对于父级元素定位。在代码中,此 li 元素具有 flex 样式,以使其内容居中。
-
有一点比较棘手:我们将在第一级之后覆盖下拉菜单的位置。这是因为之后的下拉菜单必须向右移动。
如果您希望下拉菜单趋于底部,可以忽略此处的“left”选择器。但是您不能忽略 top 属性,否则下拉菜单会重叠。
这是为嵌套下拉菜单提供正确位置的代码部分:
// every dropdown after the first level
// must pair the top with its parent,
// and must tend to the right
ul {
top: -2%;
left: 100%;
transform: translate(0)
}
最后,我们将在鼠标悬停在其父级上时显示下拉菜单:
// when hover an li (not an <a>)
// must show its ul (dropdown)
&:hover {
> ul {
opacity: 1;
visibility: visible;
transition-delay: 0ms;
}
}
完毕
最后,您必须有以下代码:
.menu {
// define the height of the menu
--menu-height: 40px;
// holder and ul general style
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100vw;
ul {
list-style: none;
padding: 16px;
margin: 0;
li, li a {
opacity: .8;
color: #ffffff;
cursor: pointer;
transition: 200ms;
text-decoration: none;
white-space: nowrap;
font-weight: 700;
&:hover {
opacity: 1;
}
a {
display: flex;
align-items: center;
height: 100%;
width: 100%;
}
}
// lets put an arrow down
// to the li`s with dropdown
li {
padding-right: 36px;
&::before {
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFA500;
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
}
.link {
// links dont need arrow
&::before {
padding-right: 0;
display: none;
}
}
}
// the first ul inside the container
// is the menu, so must be visible
// and have its own style
> ul {
display: flex;
height: var(--menu-height);
align-items: center;
background-color: #000000;
// the first ul elements can be a
// link or an li with a nested ul.
// the nested ul will be a dropdown
li {
position: relative;
margin: 0 8px;
// the dropdown style
ul {
visibility: hidden;
opacity: 0;
padding: 0;
min-width: 160px;
background-color: #333;
position: absolute;
top: calc(var(--menu-height) + 5px);
left: 50%;
transform: translateX(-50%);
transition: 200ms;
transition-delay: 200ms;
// the dropdown items style
li {
margin: 0;
padding: 8px 16px;
display: flex;
align-items: center;
justify-content: flex-start;
height: 30px;
padding-right: 40px;
// lets put an arrow right
// to the inner li`s with
// dropdowns
&::before {
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 5px solid #FFA500;
}
// every dropdown after the
// first must open to the right
ul {
top: -2%;
left: 100%;
transform: translate(0)
}
&:hover {
background-color: #000000;
}
}
}
// on hover an li (not an <a>)
// must show its ul (dropdown)
&:hover {
> ul {
opacity: 1;
visibility: visible;
transition-delay: 0ms;
}
}
}
}
}
上面的 css 将允许这样的 html 结构
<div class="menu">
<ul>
<li>
Dropdown A
<ul>
<li class="link">
<a href="">Im a link</a>
</li>
<li class="link">
<a href="">Im a link</a>
</li>
<li>
Nested dropdown
<ul>
<li class="link">
<a href="">Im a link</a>
</li>
<li class="link">
<a href="">Im a link</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
这里的想法很常见也很简单,即使理解起来需要一些时间。每个列表项在鼠标悬停时都必须显示其子 ul 元素。
造成一些复杂性的原因在于我们必须针对不同级别的下拉菜单更改 CSS 规则,因为元素会改变其行为。
第一层 ul 是菜单本身,它是一个水平栏。第二层 ul 是水平居中的下拉线。再下一层 ul 是内部下拉菜单,它们向右移动,依此类推。
您可以使用 javascript 为下拉菜单添加碰撞系统,更改它们的增长方式,或将所有内容更改为汉堡菜单等。它足够简单,可以很好地扩展。
我尽力解释了,但我不想过度解释那些可能看代码就能理解的东西。对于这种东西,我相信一些例子、一个整体的解释,再加上谷歌工具,就足以理解了。
希望这最终能对初学者有所帮助。
记住:最好的老师是实践 :)
谢谢
大家的宝贵时间。就到这里。
(封面照片由 Jo Szczepanska 在 Unsplash 上拍摄)
鏂囩珷鏉ユ簮锛�https://dev.to/felipperegazio/building-a-pure-css-menu-with-nested-dropdowns-hcn