今天,我将为你提供一个关于如何建立分层导航弹出式菜单的快速教程,该菜单能够跨多个级别进行深层嵌套。css
做为抛砖引玉,咱们将从一个具体的实际用例开始 —— 一个桌面应用程序的示例菜单栏。我将选择 Chrome 浏览器菜单栏中的一个子列表来讲明这一点。html
咱们将从一个简单的界面和外观入手,源自经典的 Windows™ 主题,这里有个短视频告诉你它长什么样:前端
css-nav-menu-3.mp4android
在最后,咱们会增长一些样式,让它有点像 MacOS™ 的感受。ios
让咱们先了解一下菜单项一般由什么组成。它们应该具备如下属性:git
请注意,咱们能够继续向菜单添加更复杂的行为。例如,某个菜单能够是一个 切换 项,因此,须要某种形式的记号(✔)或与之关联的复选框,以指示其打开/关闭状态。github
咱们将使用 CSS classes 在 HTML 标记上指示这些属性,并编写一些巧妙的样式来传递全部相应的行为。web
基于上文,咱们的基本菜单 HTML 应该是什么样子:后端
ul
元素定义,单个菜单项固然是 li
。span
元素放置在 li
中的锚(a
)标签内并带有相应 CSS 类(label
或 shortcut
),因此点击它会调用导航事件,还能够提供一些 UI 反馈,例如在 Hover 时突出显示菜单项。li
元素(父)中的另外一个 ul
元素中,依此类推。这个特定的菜单项包含一个子菜单,而且可以添加一些特定的样式以使其正常工做(以及诸如 ▶ 指示符之类的可视元素,)们将向 li
此父级添加 has-children
CSS 类。li
上中添加一个名为 separator
的相应 CSS 类来表示它。disabled
CSS 类。它的做用是使此项没法响应鼠标事件,如悬停或点击。nav
容器元素中。(这样语义化很好)并为其添加 flyout-nav
类,以获取咱们将添加的CSS样式的一些基本命名空间。<nav class="flyout-nav">
<ul>
<li>
<a href="#"><span class="label">File</span></a>
<ul>
<li>
<a href="#">
<span class="label">New Tab</span>
<span class="shortcut">⌘T</span>
</a>
</li>
<li>
<a href="#">
<span class="label">New Window</span>
<span class="shortcut">⌘N</span>
</a>
</li>
<li class="separator"></li>
<li class="has-children">
<a href="#">
<span class="label">Share...</span>
</a>
<ul>
<li>
<a href="#">
<span class="label">✉️ Email</span>
</a>
</li>
<li>
<a href="#">
<span class="label">💬 Messages</span>
</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
复制代码
我撒了谎。咱们将使用 SCSS 代替。浏览器
不开玩笑了,有趣的部分来了!
默认状况下应该 隐藏 菜单(第一级 导航菜单条
除外)。
只有在使用鼠标指针悬停相应的菜单项时,才应显示第一级下的任何内容。你可能已经猜到了,为了这个咱们将严重依赖 CSS 的 hover
伪类。
理解咱们如何使子菜单位置的正确并将其自身与父菜单项对齐也许是整个谜题中最棘手的一点。这就是 CSS 定位的一些知识来源。让咱们看看这个。
咱们之因此选择将子菜单 ul
元素放在“父” li
元素中是有缘由的。固然,它有助于咱们在逻辑上适当地将分层内容的标记组合在一块儿。它还有另外一个目的,即容许咱们轻松编写一些 CSS 来相对于父元素的位置定位子元素。而后咱们将这个概念一直延伸到根元素 ul
和 li
。
为此,咱们将使用 absolute
定位和 top
的组合,left
CSS 属性将帮助咱们相对于其最近的非静态定位祖先(closest non-static positioned ancestor) 定位子元素定义包含块。非静态(non-static)的意思是元素的 CSS position 属性不是 static
(这默认发生在 HTML 文档流中),但它是 relative
、absolute
、fixed
或者 sticky
其中之一。为了确保这一点,咱们将把 position relative
分配给 li
元素,并将其子元素 ul
的 position 设置为 absolute
。
.flyout-nav {
// 任何级别的菜单项列表
ul {
margin: 0;
padding: 0;
position: absolute;
display: none;
list-style-type: none;
}
// 菜单项
li {
position: relative;
display: block;
// 显示上的下一级下拉列表
// 在同一高度的右边
&:hover {
& > ul {
display: block;
top: 0;
left: 100%;
}
}
}
复制代码
其效果以下图所示,并在红色框中突出显示以供说明。为了使图片看起来更漂亮,咱们在图片中添加了一些用于视觉样式的 CSS,可是核心行为是由上面的内容定义的。这使其在 N 层嵌套内(在实用性的限制范围内)保持良好的工做状态。
但有一个例外,即第一级菜单项列表(在咱们的示例中,File、Edit、View...),其子菜单项须要放在 下方 而不是右侧。为了处理这个问题,咱们添加了一些新的样式重写了以前的 CSS。
.flyout-nav {
// ... 其余的东西
// 一级行为的覆盖(导航菜单条)
& > ul {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: stretch;
// 应显示第一级下拉列表
// 在同一左侧位置
& > li:hover > ul {
top: 100%;
left: 0;
}
}
}
复制代码
请注意,在这里不必定非要使用弹性盒子 flex-box
,这只是我作的选择。你也可使用其余方法实现相似的行为,例如在 ul
和 li
项上组合 display: block
和 display: inline-block
。
一旦咱们完成了对菜单项定位的基本操做,咱们将继续编写一些额外的样式,如字体、大小、颜色、背景和阴影等,以使 UI 感受更好。
为了一致性和重用,咱们采起使用一组 SCSS 变量定义和共享了这些值。像这样...
// 变量
$page-bg: #607d8b;
$base-font-size: 16px; // 变成 1rem
$menu-silver: #eee;
$menu-border: #dedede;
$menu-focused: #1e88e5;
$menu-separator: #ccc;
$menu-text-color: #333;
$menu-shortcut-color: #999;
$menu-focused-text-color: #fff;
$menu-text-color-disabled: #999;
$menu-border-width: 1px;
$menu-shadow: 2px 2px 3px -3px $menu-text-color;
$menu-content-padding: 0.5rem 1rem 0.5rem 1.75rem;
$menu-border-radius: 0.5rem;
$menu-top-padding: 0.25rem;
复制代码
咱们还剩下一些部分要添加合适的样式和特性。咱们如今将会快速地把它们过一遍。
.flyout-nav {
// ... 其余的东西
li {
// ... 其余的东西
// 菜单项-文本、快捷方式信息和悬停效果(蓝色背景)
a {
text-decoration: none;
color: $menu-text-color;
position: relative;
display: table;
width: 100%;
.label,
.shortcut {
display: table-cell;
padding: $menu-content-padding;
}
.shortcut {
text-align: right;
color: $menu-shortcut-color;
}
label {
cursor: pointer;
}
// 对于切换的菜单项
input[type='checkbox'] {
display: none;
}
input[type='checkbox']:checked + .label {
&::before {
content: '✔️';
position: absolute;
top: 0;
left: 0.25rem;
padding: 0.25rem;
}
}
&:hover {
background: $menu-focused;
.label,
.shortcut {
color: $menu-focused-text-color;
}
}
}
}
}
复制代码
这段代码的大部份内容都是简单明了的。可是,你注意到什么有趣的事情了吗?关于 input[type='checkbox']
?
对于切换,咱们使用隐藏的 HTML 复选框元素来维护状态(打开或关闭)并相应地使用 ::before
伪元素为标签设置样式。咱们可使用一个简单的 CSS 相邻兄弟选择器来作到这一点。
该菜单项的相应 HTML 标记以下所示:
<li>
<a href="#">
<input type="checkbox" id="alwaysShowBookmarksBar" checked="true" />
<label class="label" for="alwaysShowBookmarksBar">Always Show Bookmarks Bar</label>
<span class="shortcut">⇧⌘B</span>
</a>
</li>
复制代码
.flyout-nav {
// ... 其余的东西
li {
// ... 其余的东西
// 分隔符项
&.separator {
margin-bottom: $menu-top-padding;
border-bottom: $menu-border-width solid $menu-separator;
padding-bottom: $menu-top-padding;
}
}
}
复制代码
.flyout-nav {
// ... 其余的东西
li {
// ... 其余的东西
// 不要让禁用的选项响应 hover
// 或者点击并给它们涂上不一样的颜色
&.disabled {
.label,
.shortcut {
color: $menu-text-color-disabled;
}
pointer-events: none;
}
}
}
复制代码
CSS pointer-events 在这有个实用的技巧。将其设置为 none
将变成不可选的鼠标事件目标对象。
如今咱们已经了解了这些构造块,让咱们把它们组合一块儿。这里有一个 CodePen 连接到咱们的多层次弹出式导航菜单的行动!
若是你不喜欢复古 Windows 的外观,这是同一代码的另外一个版本,对 CSS 进行了一些细微的调整,使其看起来和感受更像 MacOS。
示例:仅限于 CSS 的多级嵌套弹出式导航菜单(相似于 MacOS)
有一些事情咱们尚未处理。首先,
但愿这是有用的。干杯!
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。