构建带有嵌套下拉菜单的纯 CSS 菜单

2025-06-08

构建带有嵌套下拉菜单的纯 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>
Enter fullscreen mode Exit fullscreen mode

您可以在任何地方添加下拉菜单。虽然不应该这么做,但您可以这样做。这里,我们将在导航栏上添加下拉菜单。因此,“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;
}
Enter fullscreen mode Exit fullscreen mode

如果您不熟悉 css vars,可以阅读这篇文章:
https://dev.to/sarah_chima/an-introduction-to-css-variables-cmj

--menu-height 是一个 CSS 变量,用于定义导航栏的高度。它还可以用来定义导航栏的子元素位置。

现在我们有了固定导航,重要的是要注意,当瞄准其中的 ul 时,我们有三个范围:

  1. 我们可以定位所有 ul(整个范围)。对于整个范围,您只需使用“.menu ul”作为选择器即可。

  2. 我们只能定位第一个 ul(内部作用域)。它是菜单容器,也是菜单的第一级。您可以使用“.menu > ul”来选择它,我们稍后会详细讨论。

  3. 我们可以只定位嵌套的 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%;      
      }      
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,菜单内的所有 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;
      }      
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

要了解我们如何仅使用 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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

我们必须使用 > 运算符来选中菜单 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>
Enter fullscreen mode Exit fullscreen mode

下拉菜单

下拉菜单是菜单容器中列表项内的每个 <ul> 元素,也就是“.menu”div 上的第一个 ul。我们必须将它传递给 CSS。但首先,让我们了解一下 CSS 是如何处理它的:

// the menu
.menu {
  // the menu holder with options
  > ul {
     // the menu items
     li {
        // the item dropdown
        ul {
        }
     }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

这是一个很长且重要的部分。让我们来理解一下。

我们要选中列表项中的所有 ul,并将其转换为下拉菜单。要实现此目的,我们必须遵循以下步骤:

  1. 开始告诉 CSS 如何绘制嵌套的 ul 以及将其放置在何处。起初,这个 ul 是不可见的,当我们将鼠标悬停在其父元素上时才会显示出来。当 ul 可见时,以下代码将负责将所有元素放置在正确的位置:
// ...
position: absolute;
top: calc(var(--menu-height) + 5px);
left: 50%;
transform: translateX(-50%);
transition: 200ms;
transition-delay: 200ms;
Enter fullscreen mode Exit fullscreen mode

顶部将下拉菜单设置为位于导航栏之后,这就是我们设置--menu-height var 的原因。

左键 + transform 会使下拉菜单相对于其父级居中。transition 会使它变得平滑,delay 会使光标在下拉菜单之间导航有间隔。

  1. 包含下拉菜单的列表项 (ul) 必须是相对的。因此,我们可以为下拉菜单赋予绝对位置,并使其相对于父级元素定位。在代码中,此 li 元素具有 flex 样式,以使其内容居中。

  2. 有一点比较棘手:我们将在第一级之后覆盖下拉菜单的位置。这是因为之后的下拉菜单必须向右移动。

如果您希望下拉菜单趋于底部,可以忽略此处的“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)
}
Enter fullscreen mode Exit fullscreen mode

最后,我们将在鼠标悬停在其父级上时显示下拉菜单:

// when hover an li (not an <a>)
// must show its ul (dropdown)
&:hover {
  > ul {
    opacity: 1;
    visibility: visible;
    transition-delay: 0ms;
  }
}
Enter fullscreen mode Exit fullscreen mode

完毕

最后,您必须有以下代码:

.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;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

上面的 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>
Enter fullscreen mode Exit fullscreen mode

这里的想法很常见也很简单,即使理解起来需要一些时间。每个列表项在鼠标悬停时都必须显示其子 ul 元素。

造成一些复杂性的原因在于我们必须针对不同级别的下拉菜单更改 CSS 规则,因为元素会改变其行为。

第一层 ul 是菜单本身,它是一个水平栏。第二层 ul 是水平居中的下拉线。再下一层 ul 是内部下拉菜单,它们向右移动,依此类推。

您可以使用 javascript 为下拉菜单添加碰撞系统,更改它们的增长方式,或将所有内容更改为汉堡菜单等。它足够简单,可以很好地扩展。

我尽力解释了,但我不想过度解释那些可能看代码就能理解的东西。对于这种东西,我相信一些例子、一个整体的解释,再加上谷歌工具,就足以理解了。

希望这最终能对初学者有所帮助。
记住:最好的老师是实践 :)

谢谢
大家的宝贵时间。就到这里。

(封面照片由 Jo Szczepanska 在 Unsplash 上拍摄)

鏂囩珷鏉ユ簮锛�https://dev.to/felipperegazio/building-a-pure-css-menu-with-nested-dropdowns-hcn
PREV
Então você quer ser Dev?结论
NEXT
学习 React - 第 1 部分 - 使用 Webpack 4 (+ CSS / SASS) 简单设置 React 应用程序