Bootstrap 4 Nav(布局)解析





5.00/5 (6投票s)
关于 Bootstrap 4 导航栏布局的文章:是什么让它像现在这样工作?
Content
引言
没有导航栏的网站会是什么样?什么也不是!无法导航到其他部分,等等。
在我的 Bootstrap 4 及其功能的调查中,我将剖析 Bootstrap 4 Nav 响应式导航栏。Bootstrap Nav 栏允许您定义一个响应式导航栏:它可以在不同的屏幕尺寸上表现不同。我将采用与我的 Grid 文章类似的方法:从简单的构造开始,解释它们如何运作,然后逐步构建更复杂的导航栏以及它们如何运作。
我不会解释允许您选择导航栏使用的配色方案的样式类。对于具有 CSS 基础知识的人来说,它们应该没有什么令人惊讶的地方。而且我也不想让本文的范围太广。
同样,假定您具备 HTML 和 CSS 的基础知识。
让我们开始吧。
构建基础:我们能做什么
导航栏:一切都关乎条和项
导航栏的基本结构是导航栏本身以及内部的导航项。
一个非常基本的标记是
<h1><a name="basic"></a>A basic navigation bar</h1>
<nav class="navbar navbar-light bg-light">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#">Item3 Disabled</a>
</div>
</nav>
查看时可能会有一个惊喜:所有内容都垂直显示。导航栏中的项目显示为垂直行,而不是水平相邻。另一方面,这也不应该那么令人惊讶:Bootstrap 强调*移动优先*,您可能不希望在屏幕宽度有限的移动设备上将导航栏中的所有项目并排显示。
我们当然可以在一个 `navbar` 中放置多个 `navbar-nav`
<h1><a name="basicmulti"></a>A basic navigation bar (multiple groups)</h1>
<nav class="navbar navbar-light bg-light">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1.1 Active</span></a>
<a class="nav-item nav-link" href="#">Item1.2</a>
<a class="nav-item nav-link disabled" href="#">Item1.3 Disabled</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Item2.1</span></a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Item3.1</span></a>
<a class="nav-item nav-link" href="#">Item3.2</a>
</div>
</nav>
请注意,多个 `navbar-nav` 如何均匀地分布在父 `navbar` 的宽度上。
实现响应式:定义响应式断点
如果您希望导航栏中的项目在更大的屏幕上水平流动,您可以使用以下代码
<h1><a name="responsive"></a>A responsive navigation bar</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Item3 Disabled</a>
</div>
</nav>
您需要缩小或放大浏览器窗口的宽度才能看到效果:导航栏中的项目从较宽屏幕宽度上的水平布局过渡到较窄屏幕宽度上的垂直堆叠。
请注意 `navbar-expand-lg` 类的添加。“`lg`”部分定义了响应式断点:它定义了导航栏何时从垂直行展开为水平布局的宽度。您可以使用以下值
- `sm`:大于或等于 576px
- `md`:大于或等于 768px
- `lg`:大于或等于 992px
- `xl`:大于或等于 1200px
因此,在上面,当浏览器窗口超过 992px 时,我们从行过渡到水平布局。
当然,如果我们在移动设备上这样做,用户首先看到的是导航栏垂直堆叠的行,而不是您网站的内容。尽管导航很重要,但您的内容才是访问者想看到的。我们想要一种方法使导航项出现和消失。请看 `navbar-toggler`
<h1><a name="responsivebtn"></a>A responsive navigation bar with toggle button</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent_resptb"
aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent_resptb">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Item3 Disabled</a>
</div>
</div>
</nav>
首先:`navbar-toggler` 内部使用 JavaScript。因此您需要在页面上引用 Bootstrap JavaScript 库。您可以在示例页面中看到这是如何完成的。
其次,除了添加的 `button` 本身,这里重要的是按钮上的 `data-toggle` 和 `data-target` 属性,以及 `div` 上添加的 `id` 属性和 `collapse` 类。`id` 属性的值必须与按钮上 `data-target` 属性的值相同。`data-target` 属性定义要隐藏的元素。
实际发生的事情很简单:通过 JavaScript,按钮点击被拦截,`div` 的 `display` 属性在隐藏的 `none` 和显示的 `block` 之间切换。
解构基础:它如何运作?
如果您阅读了关于 Bootstrap 4 网格 的文章,您就占有优势:导航栏也基于 CSS3 flexbox 模型。如果您没有,请继续阅读。如果您有:也请继续阅读,因为我还会解释其他事情。
有一些构造使导航栏正常工作
- CSS flexbox 系统
- CSS 选择器:选择 HTML 元素层级
- 响应式断点
- 一点 JavaScript 魔法来修改 HTML 元素上的类
解构基本导航栏
让我们从查看基本导航栏的 CSS 开始(我将只显示对讨论重要的属性)
.navbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.navbar-nav {
display: flex;
flex-direction: column;
}
好的,我们使用 `display: flex` 和 `flex-direction` 为 `column` 类型定义了一个 `flexbox`。这有什么作用?
CSS Flexbox 系统:管理 Flexbox 内部的流
首先,我要说 CSS flexbox 系统是一个庞大的主题。因此,我将只解释与导航栏直接相关的内容。这与 Bootstrap 4 网格 文章中对 `flexbox` 系统的解释会有一些重叠。
那么,让我们从一个基本的 `flexbox` 设置开始
.simple-flexbox {
display:flex;
}
<h1><a name="flowdir"></a>Managing the flow direction</h1>
<div class="simple-flexbox parent-container">
<div>Default Flexbox subitem 1</div>
<div>Default Flexbox subitem 2</div>
<div>Default Flexbox subitem 3</div>
</div>
`display: flex` 使其应用的 `div` 的子元素布局方式与我们习惯的不同。通常,`div` 内部的元素垂直堆叠。但是 `flexbox` 将此默认值更改为水平。此外,在默认布局中,子 `div` 占据屏幕的整个宽度,而在 `flexbox` `div` 中,它们只占用必要的空间。
上面是默认 `flexbox` 布局的情况。然而,有一大堆相关属性允许您更改这种行为。让我们尝试其中的一些。
首先,flex 容器内布局的方向。默认是水平的,但我们可以将其更改为我们喜欢的任何方向
.vertical-flexbox {
display:flex;
flex-direction: column;
}
<h1><a name="flowdir"></a>Managing the flow direction</h1>
<div class="vertical-flexbox parent-container">
<div class="bg-red">Vertical Flexbox subitem 1</div>
<div class="bg-green">Vertical Flexbox subitem 2</div>
<div class="bg-blue">Vertical Flexbox subitem 3</div>
</div>
瞧,垂直堆叠的 `div` 占据了整个窗口宽度。
就像我们基本导航栏中的那样。
您还可以管理子元素的对齐方式。您可以通过两个方向管理对齐。第一个方向是 `flexbox` 的流向。第二个方向垂直于流:所谓的*交叉方向*。
要管理流方向的对齐,我们有 `justify-content` 属性。
.flexbox-justify-end {
justify-content: flex-end;
}
.flexbox-justify-between {
justify-content: space-between;
}
<h1>Managing the layout of flex children: in the direction of the flow
for horizontal flow</h1>
<div class="simple-flexbox parent-container flexbox-justify-end">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
<div class="simple-flexbox parent-container flexbox-justify-between">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
<h1>Managing the layout of flex children: in the direction of the flow
for vertical flow</h1>
<div class="vertical-flexbox height-100 parent-container flexbox-justify-end">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
<div class="vertical-flexbox height-100 parent-container flexbox-justify-between">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
我认为这做什么很明显。此属性还有其他一些可能的值,您可以在参考部分查看互联网上提供 `flexbox` 系统完整描述的地方。`navbar` 类的标准对齐方式是 `justify-content: space-between`,这意味着多余的空间均匀分布在 `flexbox` 的子元素之间。这就是为什么在带有分组的基本导航栏中,不同的组会分布在屏幕的总宽度上。
但请注意,对于 `column` `flexbox` 方向,我们必须明确设置 `flexbox` 容器的高度才能演示设置!
您还可以通过使用 `align-items` 属性来管理 `flexbox` 中项目在交叉方向上的对齐方式
.width-33percent {
width: 33%;
}
.flexbox-align-top {
align-items: flex-start;
}
.flexbox-align-center {
align-items: center;
}
<h1>Managing the layout of flex children: in the cross direction for horizontal flow</h1>
<div class="simple-flexbox parent-container flexbox-align-top">
<div class="width-33percent bg-red">Flexbox subitem 1 with extra text ...
to trigger overflow</div>
<div class="width-33percent bg-green">Flexbox subitem 2</div>
<div class="width-33percent bg-blue">Flexbox subitem 3</div>
</div>
<div class="simple-flexbox parent-container flexbox-align-center">
<div class="width-33percent bg-red">Flexbox subitem 1 with extra text ...
to trigger overflow</div>
<div class="width-33percent bg-green">Flexbox subitem 2</div>
<div class="width-33percent bg-blue">Flexbox subitem 3</div>
</div>
<h1>Managing the layout of flex children: in the cross direction for vertical flow</h1>
<div class="vertical-flexbox parent-container flexbox-align-top">
<div class="width-33percent bg-red">Flexbox subitem 1 with extra text ...
to trigger overflow</div>
<div class="width-33percent bg-green">Flexbox subitem 2</div>
<div class="width-33percent bg-blue">Flexbox subitem 3</div>
</div>
<div class="vertical-flexbox parent-container flexbox-align-center">
<div class="width-33percent bg-red">Flexbox subitem 1 with extra text ...
to trigger overflow</div>
<div class="width-33percent bg-green">Flexbox subitem 2</div>
<div class="width-33percent bg-blue">Flexbox subitem 3</div>
</div>
<div class="vertical-flexbox parent-container flexbox-align-center">
<div class="bg-red">Flexbox subitem 1</div>
<div class="bg-green">Flexbox subitem 2 but made a bit larger</div>
<div class="bg-blue">Flexbox subitem 3 also larger</div>
</div>
大部分内容应该很明显。但我想提请您注意两件事
- 请注意,在前两个示例中,我将第一个子 `div` 内部的文本加长,以使同级 `div` 按照 `align-items` 属性的要求定位。
- 请注意,在交叉方向垂直流动的示例中,最后两个示例清楚地显示了对齐方式是应用于子元素本身,而不是子元素内部的内容。
解构响应式导航栏
接下来是响应式导航栏。
除了上述 CSS 类,我们还有以下附加类
@media (min-width: 992px) {
.navbar-expand-lg {
flex-flow: row nowrap;
justify-content: flex-start;
}
.navbar-expand-lg .navbar-nav {
flex-direction: row;
}
}
首先,让我们回顾一下第二个定义。这可能看起来像一个奇怪的类定义:它有两个类名。这里发生了什么?
CSS 选择器:选择 HTML 元素层级
我们已经将响应式导航栏定义如下
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Item3 Disabled</a>
</div>
</nav>
相对于非响应式版本,我们添加了 `navbar-expand-lg` 类。
这会改变带有 `navbar-nav` 类的 `div` 的 CSS,以便 `flex-direction` 属性从 `column` 变为 `row`。它是如何做到的?通过使用应用于特定 HTML 元素层级的 CSS 类。
我们都熟悉传统的 CSS 类
.parentsection {
border: solid;
border-color: red;
}
.mainsection {
border: solid;
border-color: blue;
}
<div>
<div class="mainsection">
<div class="mainsection">A first subsection</div>
<div>A second subsection</div>
</div>
<div class="mainsection">A second main section</div>
</div>
<div class="parentsection">
<div class="mainsection">
<div class="mainsection">A first subsection</div>
<div>A second subsection</div>
</div>
<div class="mainsection">A second main section</div>
</div>
这里没有什么惊喜:应用了 `mainsection` CSS 类的 `div` 具有蓝色边框。
但是,如果我们希望 `parentsection` 内部带有 `mainsection` 的 `div` 的行为与那些不带 `mainsection` 的 `div` 不同,该怎么办?请看层级选择器
.parentsection {
border: solid;
border-color: red;
}
.mainsection {
border: solid;
border-color: blue;
}
.parentsection .mainsection {
border: solid;
border-color: green;
}
.something-inbetween{
border: solid;
border-color: purple;
}
<h1><a name="simple"></a>Simple nesting</h1>
<div class="parentsection">
<div class="mainsection">A mainsection inside a parentsection</div>
<div>
<div class="mainsection">A mainsection two levels deep</div>
<div>A second subsection</div>
</div>
<div class="mainsection">
<div class="mainsection">A mainsection within another mainsection</div>
<div>A second subsection</div>
</div>
<div class="something-inbetween">
<div class="mainsection">A mainsection with something inbetween</div>
<div>A second subsection</div>
</div>
</div>
这里有几点需要注意
- 声明层级时,类名之间有一个空格。
- 层级是为 `parentsection` 的每个子 `mainsection` 定义的。中间是否有其他 HTML 元素无关紧要。
- 层级是为 `parentsection` 的每个子 `mainsection` 定义的。中间是否有其他同名或不同名元素无关紧要。
当然,最后一句评论仅在没有为中间的类定义特定层级时才有效
.othersection {
border: dashed;
border-color: blue;
}
.parentsection .othersection {
border: dashed;
border-color: green;
}
.parentsection .othersection .othersection {
border: dashed;
border-color: maroon;
}
<h1><a name="complex"></a>Complex nesting</h1>
<div class="parentsection">
<div class="othersection">A othersection inside parentsection</div>
<div class="othersection">
<div class="something-inbetween">
<div class="othersection">A othersection with something-inbetween</div>
</div>
</div>
<div class="something-inbetween">
<div>A second subsection</div>
<div class="othersection">A othersection with something-inbetween</div>
<div class="othersection">
<div class="othersection">Something-inbetween a othersection
within another othersection</div>
</div>
</div>
</div>
因为我们定义了一个更具体的 `.parentsection .othersection .othersection` 嵌套,所以第二个 `div`(带有文本 *A othersection with something-inbetween*)和最后一个 `div`(带有文本 *Something-inbetween a othersection within another othersection*)有一个栗色边框。
在嵌套类的定义中,类名之间的空格很重要。如果省略空格,含义就不同了:那么所有类都必须应用于单个元素(但它们之间有空格)
.se_first_part {
border: dashed;
border-color: mediumvioletred;
}
.se_second_part {
border: dashed;
border-color: olive;
}
.se_first_part.se_second_part {
border: double;
border-color:turquoise;
}
<h1><a name="singleelem"></a>Single element target</h1>
<div>
<div class="se_first_part se_second_part">A first_part
and a second_section together</div>
<div class="se_first_part">
<div class="se_second_part">A second part_nested within a first_part</div>
<div>A subsection</div>
</div>
</div>
我在这里重复 Bootstrap CSS 的一部分
@media (min-width: 992px) {
.navbar-expand-lg .navbar-nav {
flex-direction: row;
}
}
我们在这里说的是,对于 `navbar-expand-lg` 内部的所有 `navbar-nav`,`flex-direction` 属性应设置为 `row`,从而使项目水平呈现。
但还有更多事情正在发生。
CSS Flexbox 系统:管理 Flexbox 内部的换行
我们重复一些 CSS
.navbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
@media (min-width: 992px) {
.navbar-expand-lg {
flex-flow: row nowrap;
justify-content: flex-start;
}
}
我们现在已经了解了 `justify-content` 和 `align-items` 属性:它们管理 `flexbox` 子项的水平流。
但是这个 `flex-wrap` 属性是什么呢?
.flexbox-wrapping {
flex-wrap: wrap;
}
.flexbox-nowrapping {
flex-wrap: nowrap;
}
<h1>Managing content wrapping</h1>
<div class="simple-flexbox flexbox-wrapping">
<div class=" bg-red">Flexbox subitem 1</div>
<!-- a lot more divs here in the sample code -->
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
<div class="simple-flexbox flexbox-nowrapping">
<div class=" bg-red">Flexbox subitem 1</div>
<!-- a lot more divs here in the sample code -->
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
现在,水平拉伸和缩小您的浏览器窗口。
这应该能弄清楚:`wrap` 允许 `flexbox` 的内容在超出 `flexbox` 总宽度时换行。`nowrap` 会阻止这种情况,而是换行子 `div` 的内容。
因此,应用 `navbar-expand-lg` 类会阻止应用于它的 `flexbox` 内部的项目换行。`flex-flow` 属性实际上是 `flex-direction` 和 `flex-wrap` 属性组合的快捷方式。
响应式断点
那么 `navbar-expand-lg` 定义在其中的这个奇怪的 `@media` 东西是什么?这就是响应式断点,它确保 CSS 类仅适用于屏幕宽度大于 992px 的情况。
在这里我将偷个懒,将您转到我关于 [Bootstrap 3 网格](https://codeproject.org.cn/Articles/1109210/The-Bootstrap-Grid-Deconstructed#toc132) 的文章,其中我详细解释了响应式断点。
解构带切换按钮的响应式导航栏
最后是一个带切换按钮的响应式导航栏。
.collapse:not(.show) {
display: none;
}
.navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
align-items: center;
}
@media (min-width: 992px) {
.navbar-expand-lg .navbar-collapse {
display: flex !important;
flex-basis: auto;
}
.navbar-expand-lg .navbar-toggler {
display: none;
}
}
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Item3 Disabled</a>
</div>
</div>
这里的一切都应该很清楚,除了以下几点
- 这个 `:not(.show)` 是什么东西?
- `button` 元素上的那些 `data` 属性是什么?
(我知道:`flex-basis` 和 `flex-grow` 是什么意思?我现在将跳过它们,因为对于我们现在正在构建的简单导航栏来说它们不重要。但我在讨论更复杂的布局时会回到它们。)
CSS 选择器:not() 选择器
我们已经了解了如何选择 HTML 元素层级。但是有一整套语法以及运算符可以用于选择页面上的元素以应用 CSS。
其中一个运算符就是 `:not()` 运算符。它允许您从选定的一组元素中排除特定元素。一个例子将使一切都清楚
.bordered:not(.noborder) {
border: solid;
border-color: blue;
}
<div class="bordered">
A bordered section
</div>
<div class="bordered noborder">
A not bordered section
</div>
这里发生的事情应该很清楚:通过应用 `:not()` 选择器,我们可以通过向元素添加另一个 CSS 类来排除该元素受 CSS 类影响。还要注意,在 `:not()` 运算符中指定的 CSS 类甚至不需要定义!
好的,那么这些 `data` 属性是什么呢?
一点 JavaScript 魔法来修改 HTML 元素上的类
首先:`data-*` 样式属性是向元素添加额外数据的标准化方式。有关更详细的讨论,请参阅参考部分。
Bootstrap JavaScript 库利用这些属性来查找按钮并使其作用于某些 HTML 元素。
它基本上归结为以下步骤
- 在页面上查找所有具有特定引用属性且该属性具有特定值的元素(在下面的示例中名为 `data-search`,值为 `searchForMe`)
- 对于这些元素,获取另一个属性的值(在下面的示例中名为 `data-refid`)
- 向找到的元素附加一个点击处理程序,该处理程序向 `id` 等于 `data-refid` 属性值的元素添加或删除一个控制所需属性的 CSS 类(在下面的示例中名为 `el-frame-violet`)。
(我在这里故意使用了与 Bootstrap 使用的属性不同的名称,以表明这些名称是什么并不重要。事实上,它们甚至不需要以字符串 `data` 开头!该 `string` 只是一种统一的方式,用于向 HTML 元素添加其 HTML 规范之外的特定属性。)
.el-frame-violet {
border: solid;
border-color: violet;
}
<button data-search="searchForMe" data-refid="actOnMe1" >Button 1</button>
<button data-search="searchForMe" data-refid="actOnMe2" >Button 2</button>
<div>
<div id="actOnMe1" class="bg-red">Target of button 1</div>
<div id="actOnMe2" class="bg-green">Target of button 2</div>
<div class="bg-blue">No target</div>
</div>
<script>
console.log("Running the code...");
// Find all elements on the page with a certain reference attribute
// which has a certain value
let allAffectedButtons = document.querySelectorAll('[data-search="searchForMe"]');
allAffectedButtons.forEach(function(button) {
console.log("Button: " + button.innerHTML);
// For these, get the value of the another attribute
let btnTarget = button.getAttribute("data-refid");
// Attach a click handler to those found elements
button.addEventListener("click", function(){
console.log("Button target (from the click handler): " + btnTarget);
// which adds or removes a CSS class which controls the desired properties
// to the element with an id equal to the value of the
// data-refid attribute
document.getElementById(btnTarget).classList.toggle('el-frame-violet');
});
})
</script>
上面的代码没有什么需要过多解释的。也许有两点
- 选择元素的语法是一种微型语言,它允许您根据元素是否具有某个属性、具有某个值的某个属性等来选择元素。在上面的示例中,我们搜索所有带有 `data-search` 属性且值为“`searchForMe`”的元素。
- JavaScript 定义在文档末尾:因为 `script` 标签中的代码会在页面处理时执行,如果我们将其定义在文档主体之前,按钮将不会被找到,因为它们在那时还不存在。
为什么使用 navbar-collapse 而不是 navbar-nav?
让我们尝试使用 `navbar-nav` 创建一个可折叠的导航栏
<h1><a name="responsivebtnwrong"></a>A responsive navigation bar
with toggle button NOT USING navbar-collapse ???</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent_btn_wrong"
aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-nav" id="navbarSupportedContent_btn_wrong">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#">Item1 Active</span></a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Item3 Disabled</a>
</div>
</div>
</nav>
在响应式断点之上,我们什么也看不见!!!发生了什么?
好吧,我们有以下按应用顺序排列的 CSS(请参阅参考部分以了解此顺序是如何确定的)
.navbar-nav {
display: flex;
flex-direction: column;
}
.collapse:not(.show) {
display: none;
}
@media (min-width: 992px) {
.navbar-expand-lg .navbar-nav {
flex-direction: row;
}
}
正如您所看到的,`collapse` 类将 `display` 属性设置为 `none`,从而有效地隐藏了内容。由于 `collapse` 类是在 `navbar-nav` 类之后应用的,因此 `navbar-nav` 类的 `display` 属性未被使用。但是,如果我们将页面宽度缩小到响应式断点以下,按钮就会出现。当我们点击它时,`show` 类会添加到具有 `collapse` 类的元素上,从而有效地移除该元素的 `collapse` 类,然后 `navbar-nav` 类的 `display` 属性才会生效。该元素显示为 `flexbox` 容器。
然而,通过使用 `navbar-collapse` 类,我们按应用顺序得到
.navbar-collapse {
/* does not contain any display properties */
}
.collapse:not(.show) {
display: none;
}
@media (min-width: 992px) {
.navbar-expand-lg .navbar-collapse {
display: flex !important;
}
}
正如您所看到的,在响应式断点之上,`display: flex` 值应用于 `navbar-expand-xx` 响应式类中使用的 `navbar-collapse`。这会遮盖 `collapse` 类的 `display: none` 值。在响应式断点之下,`.navbar-expand-lg .navbar-collapse` 的层级定义不为人知,并应用了 `collapse` 类的 `display: none` 值。
构建复杂导航栏
当然,导航栏还有更多功能。
更多对齐方式
顶级 `navbar` 项的默认方向为行,因此将其所有子项放在一行。但是,您可以强制其方向为列
<h1><a name="colcent"></a>A navigation bar with direction
column center aligned (with branding)</h1>
<nav class="navbar flex-column navbar-light bg-light">
<a class="navbar-brand">My Brand</a>
<div class="navbar-nav flex-row">
<a class="nav-item nav-link" href="#">Centered1</a>
<a class="nav-item nav-link" href="#">Centered2</a>
<a class="nav-item nav-link" href="#">Centered3</a>
</div>
</nav>
这里有几点需要注意
- `flex-column` 类强制其所应用的 `flexbox` 的方向为列,从而将其所有子项放在一列。
- `navbar` 内部的项目水平居中。
我们当然可以改变对齐方式
<h1><a name="colend"></a>A navigation bar with direction column end aligned
(with branding)</h1>
<nav class="navbar flex-column align-items-end navbar-light bg-light">
<a class="navbar-brand">My Brand</a>
<div class="navbar-nav flex-row">
<a class="nav-item nav-link" href="#">Item1</a>
<a class="nav-item nav-link" href="#">Item2</a>
<a class="nav-item nav-link" href="#">Item3</a>
</div>
</nav>
通过应用 `align-items-end` 类,`flexbox` 的子元素会与 flexbox 的末端对齐。还要注意这种对齐方式是如何工作的:它垂直于 `flexbox` 的方向!如果我们要移除 `flex-column` 类,`align-items-end` 在这种特定布局中将不起作用。
更多间距
一种流行的布局是将一些导航项放在左侧,一些放在右侧。这如何实现?有几种可能性。
第一种是使用 `justify-content-between` 类
<h1><a name="multbetween"></a>A second navigation bar (with content between)</h1>
<nav class="navbar navbar-expand-lg justify-content-between navbar-light bg-light">
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1</a>
<a class="nav-item nav-link" href="#">Right2</a>
</div>
</nav>
这样做会将多余的空间均匀地分布在应用此类的元素的子元素之间。
然而,当在响应式断点宽度以下查看时,这可能不会像您预期的那样:两个子 `navbar-nav` 项的子项都排成一行,但由于 `navbar` 本身默认是行样式方向,`navbar-nav` 自身会渲染在父容器的两端:由于其父元素上的 `justify-content-between`,所有多余的空间都放在它们之间。
解决方案很简单:将两个子元素放在它们自己的父 `navbar-nav` 内部。
<h1><a name="multcolbetween"></a>A second navigation bar with collapsing
(with content between)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav flex-fill justify-content-between">
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1</a>
<a class="nav-item nav-link" href="#">Right2</a>
</div>
</div>
</nav>
请注意 `flex-fill` 类的使用。在这种情况下,这很重要,因为它告诉应用它的元素占据其父 flexbox(在本例中为顶级 `nav` 元素)内部的整个空间。由于 `flexbox` 的工作方式,如果方向设置为行,则 flexbox 容器的子元素总是只占用它们所需的空间,而不是整个宽度。
第二种是使用 `mr-auto` 或 `ml-auto` 类
<h1><a name="multcolauto"></a>A second navigation bar with collapsing (with mr-auto)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav flex-fill">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1</a>
<a class="nav-item nav-link" href="#">Right2</a>
</div>
</div>
</nav>
这些操作的目的是让右侧(对于 `mr-auto`)或左侧(对于 `ml-auto`)的边距占据剩余空间。在上面,我立即将所有内容放在一个父 `navbar-nav` 内部,因为否则我们在这里会遇到与 `justify-content-between` 示例相同的问题。
在响应式断点以下布局时,还需要考虑 `mr-auto` 和 `ml-auto` 之间的选择
<h1><a name="multmlauto"></a>A second navigation bar with collapsing
(with ml-auto, but probably not what you want below the responsive breakpoint)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav flex-fill">
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav ml-auto">
<a class="nav-item nav-link" href="#">Right1</a>
<a class="nav-item nav-link" href="#">Right2</a>
</div>
</div>
</nav>
这会将边距放在应用该元素的左侧。尽管在响应式断点上方行为相同,但在断点下方,第二个 `navbar-nav` 被推到屏幕的右侧,而第一个 `navbar-nav` 位于屏幕的左侧。
对于上述布局,使用 `justify-content-between` 和 `mr-auto` 会产生相同的行为,但两者之间存在根本区别,这在处理两个以上子 `navbar-nav` 时会显现出来
<h1><a name="mult3between"></a>A second navigation bar with
3 subsections and collapsing (with content between)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav flex-fill justify-content-between">
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1.1</a>
<a class="nav-item nav-link" href="#">Right1.2</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right2.1</a>
<a class="nav-item nav-link" href="#">Right2.2</a>
</div>
</div>
</nav>
<h1><a name="mult3auto"></a>A second navigation bar with
3 subsections and collapsing (with mr-auto)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-nav flex-fill">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1.1</a>
<a class="nav-item nav-link" href="#">Right1.2</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right2.1</a>
<a class="nav-item nav-link" href="#">Right2.2</a>
</div>
</div>
</nav>
正如您所看到的,通过使用 `justify-content-between` 类,当子元素超过两个时,空间会在子元素之间均匀分布。通过使用 `mr-auto`,所有多余的空间都被第一个子元素的边距占据,从而将第二个和第三个子元素一直推向右侧。
我们能用按钮做同样的事情吗?是的,我们可以
<h1><a name="multbetweenbtn"></a>A third navigation bar (with a button)</h1>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent_btn"
aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-between"
id="navbarSupportedContent_btn">
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Left1</a>
<a class="nav-item nav-link" href="#">Left2</a>
<a class="nav-item nav-link" href="#">Left3</a>
</div>
<div class="navbar-nav">
<a class="nav-item nav-link" href="#">Right1</a>
<a class="nav-item nav-link" href="#">Right2</a>
</div>
</div>
</nav>
但是请注意,我们这里不需要 `flex-fill` 类。您将在下一节中找到原因。
解构复杂导航栏
解构更多对齐方式
从简单示例中还记得 `navbar` 类的定义方式吗?
.navbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
当我们指定 `column` 类型的显式方向时,项目最终居中,这当然是因为 `align-items: center` 属性。请记住 `align-items` 在交叉方向上工作,在流方向为 `column` 的情况下是水平的。
解构更多间距
我们已经在简单导航栏部分看到了如何在流向中进行对齐。然而,当我们在嵌套 flexbox 中尝试 flex 方向的对齐时,您可能不会得到您期望的结果
<h1>Managing the layout of flex children: in the direction of the flow
for nested horizontal flow flexboxes</h1>
<div class="simple-flexbox parent-container">
<div class="simple-flexbox parent-container flexbox-justify-end">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
</div>
<div class="simple-flexbox parent-container">
<div class="simple-flexbox parent-container flexbox-justify-between">
<div class=" bg-red">Flexbox subitem 1</div>
<div class=" bg-green">Flexbox subitem 2</div>
<div class=" bg-blue">Flexbox subitem 3</div>
</div>
</div>
尽管我们基本上应用了与简单导航栏解构中相同的内容对齐,但这些 flexbox 没有区别:它们都从头开始。发生了什么?
在 flexbox 内部,子元素只占用它们所需的空间。在上面,子 flexbox 只需要其三个子元素的空间。这就是为什么这两个构造会产生相同的结果。
我们可以通过显式设置内部 `flexbox` 的宽度来改变这种行为。在上面的 `navbar` 示例中,我们通过 `flex-fill` 类来完成此操作。
解构 Flexbox 子元素尺寸调整
以下是 `flex-fill` 类的定义
.flex-fill {
flex: 1 1 auto !important;
}
这实际上是其他三个 `flexbox` 属性的快捷表示法
.flex-fill {
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;
}
我假设您阅读了有关响应式断点的部分,所以您知道 `important!` 构造的作用。
好的,我们从 `flex-basis` 开始。
此属性允许您定义流向中的维度如何定义。它可以是带单位的数字,也可以是 `auto`。如果您使用带单位的数字,它会覆盖应用于该元素的任何其他维度设置。如果将其设置为 `auto`,则元素会根据其内容自动采用指定在元素上的维度或大小。
.width-100 {
width: 100px;
}
.flexitem-auto {
flex-basis: auto;
}
.flexitem-dim {
flex-basis: 150px;
}
.flexitem-dimpercentage {
flex-basis: 33%;
}
.flexitem-dimpercentage-large {
flex-basis: 60%;
}
<h1><a name="sizebasis"></a>Managing the size of flex children</h1>
<div class="simple-flexbox">
<div class="width-100 bg-red">Flexbox subitem 1</div>
<div class="width-100 bg-green">Flexbox subitem 2</div>
<div class="width-100 bg-blue">Flexbox subitem 3</div>
</div>
<div class="simple-flexbox">
<div class="width-100 bg-red">Flexbox subitem 1</div>
<div class="flexitem-auto bg-red">Flexbox subitem 2</div>
<div class="flexitem-dim width-100 bg-green">Flexbox subitem 3</div>
<div class="flexitem-dimpercentage width-100 bg-blue">Flexbox subitem 4</div>
</div>
<div class="vertical-flexbox">
<div class="flexitem-dim bg-green">Flexbox subitem 2</div>
<div class="flexitem-dimpercentage bg-blue">Flexbox subitem 3</div>
</div>
那么,这里发生了什么?
第一个 `div` 只是以常规方式定义宽度:这里没什么特别的。
在上述描述中,我故意避免谈论元素的宽度和高度,而是使用了更抽象的“维度”。`flex-basis` 属性在 `flexbox` 的流向中起作用。这意味着当流是水平的时,它控制宽度;当流是垂直的时,它控制高度。
第二个 `div` 使用 `flex-basis` 属性来定义和覆盖标准 `width` 属性。正如您所看到的,此属性中定义的宽度优先于正常的 `width`。它也可以具有 `auto` 的文本值,这意味着:取通常计算的 `width` 属性值,该值基于应用于该元素的内部内容,或者如果明确定义,则取 `width` 属性的值。
第三个 `div` 做了类似的事情,但在垂直流的 flex `div` 中。请注意,定义为像素值的 `flex-basis` 的项目现在如何应用于其高度!
接下来是 `flex-grow` 和 `flex-shrink` 属性。
.flexitem-expand-grow1 {
flex-basis: auto;
flex-grow: 1;
}
.flexitem-expand-grow2 {
flex-basis: auto;
flex-grow: 2;
}
.flexitem-collapse-noshrink {
flex-shrink: 0;
}
.flexitem-collapse-shrink1 {
flex-basis: 60%;
flex-shrink: 1;
}
.flexitem-collapse-shrink2 {
flex-basis: 60%;
flex-shrink: 2;
}
<h1>Managing the size of flex children: excess or shortage of space</h1>
<div class="simple-flexbox">
<div class="flexitem-expand-grow1 bg-red">Flexbox subitem 1</div>
<div class="flexitem-expand-grow2 bg-green">Flexbox subitem 2</div>
<div class="bg-blue">Flexbox subitem 3</div>
</div>
<div class="simple-flexbox">
<div class="flexitem-collapse-noshrink bg-red">Flexbox subitem 1</div>
<div class="flexitem-dimpercentage-large flexitem-collapse-noshrink bg-green">
Flexbox subitem 2</div>
<div class="flexitem-dimpercentage-large flexitem-collapse-noshrink bg-blue">
Flexbox subitem 3</div>
</div>
<div class="simple-flexbox">
<div class="flexitem-collapse-noshrink bg-red">Flexbox subitem 1</div>
<div class="flexitem-dimpercentage-large flexitem-collapse-shrink1 bg-green">
Flexbox subitem 2</div>
<div class="flexitem-dimpercentage-large flexitem-collapse-shrink2 bg-blue">
Flexbox subitem 3</div>
</div>
`flex-grow` 属性管理 `flexbox` 内部项目之间多余空间的分配。正如您从第一个示例中看到的,尽管我们指定了 `flex-basis` 为 auto,并且期望子元素只占用它们所需的空间,但由于前两个项目上的 `flex-grow` 属性,多余的空间分布在这些项目之间。更重要的是:空间按照这些属性分配的数字按比例分布。
相反,`flex-shrink` 属性调节当空间不足以容纳所有子元素时 `flexbox` 子元素的收缩。同样,项目收缩的量由 `flex-shrink` 属性的值设置。
关于计算的详细分析以及演示可以在 Bootstrap 4 Grid 文章中找到。
解构更多间距(续)
所以,回到间距和嵌套 `flexbox`。
在复杂的导航栏示例中,我们将嵌套的 `navbar-nav` 包装在一个父 `navbar-nav` 中,并为其设置了 `flex-fill` 类。如上所示,`flex-fill` 类看起来像这样
.flex-fill {
flex: 1 1 auto !important;
}
现在,我们可以理解它的工作原理了:因为通过使用这个类,我们将嵌套 flexbox 的 `flex-grow` 属性设置为 1,它占据了其父级中可用的多余空间,从而占据了窗口的全部宽度。正因为如此,嵌套 `flexbox` 的子元素现在可以应用 `justify-content` 属性并产生可见的结果。
那 `mr-auto` 和 `ml-auto` 类呢?
.mr-auto {
margin-right: auto !important;
}
.ml-auto {
margin-left: auto !important;
}
这些会将应用于它们的元素的边距设置为占据父元素中剩余的空间。然而,有一个注意事项
.width-100 {
width: 100px;
}
.marginleft-auto {
margin-left: auto;
}
让我们先了解一下这在非 flexbox 元素中有什么作用
<h1><a name="automargin_noflexbox"></a>Applying auto margins outside a flexbox</h1>
<div>Some text and then suddenly a <span class="marginleft-auto bg-red">
span with left margin set to auto</span> and then some more text:
nothing is happening!</div>
<div>
<div class="marginleft-auto bg-red">Some nested div inside a parent div</div>
<div class="width-100 marginleft-auto bg-blue">Some nested div
inside a parent div set to a fixed width</div>
</div>
请注意
- 对于内联内容(如 `span` 元素),将边距设置为 `auto` 解析为值 0:不应用任何边距。
- 对于块内容(如 `div` 元素),将边距设置为 `auto` 也无效,因为这些元素通常会占据其父元素的全部宽度。
- 对于具有指定宽度的块内容,边距设置确实会生效:边距占据所有剩余空间。
现在,对于 `flexbox` 元素
<h1><a name="automargin_flexbox"></a>Applying auto margins inside a flexbox</h1>
<div class="simple-flexbox">
<div class="bg-red">Flexbox subitem 1</div>
<div class="marginleft-auto bg-blue">Flexbox subitem 2</div>
</div>
<div class="simple-flexbox">
<div class="bg-red">Flexbox subitem 1</div>
<div class="marginleft-auto bg-blue">Flexbox subitem 2</div>
<div class="marginleft-auto bg-green">Flexbox subitem 3</div>
</div>
<div class="vertical-flexbox">
<div class="bg-red">Flexbox subitem 1</div>
<div class="marginleft-auto bg-blue">Flexbox subitem 2</div>
</div>
请注意
- 对于 `row` 流,如果多个子元素将其边距设置为 `auto`,则多余空间将均匀分布在这些子元素之间。
- 对于 `column` 流,如果我们对 `margin-left` 或 `margin-right` 属性使用 `auto` 设置,这实际上是在交叉方向上。但边距仍然会应用。
解构带切换按钮的响应式导航栏:我之前没告诉你的...
还记得我解释带有按钮的响应式导航栏时忽略了一些属性吗?
请注意,在带有按钮的导航栏中,我们定义了两个子 `navbar-nav`,设置了 `justify-content-between`,瞧,我们有了独立的子组。正如您所预料的,不需要 `flex-fill`。这里发生了什么?
CSS 类的定义将立即澄清一切
.collapse:not(.show) {
display: none;
}
.navbar-collapse {
flex-basis: 100%;
flex-grow: 1;
align-items: center;
}
@media (min-width: 992px) {
.navbar-expand-lg .navbar-collapse {
display: flex !important;
flex-basis: auto;
}
}
请注意 `navbar-collapse` 类上的 `flex-grow` 属性:不需要 `flex-fill`,因为设置已经在 `navbar-collapse` 类本身中完成!
还要注意 `navbar-collapse` 类上的 `flex-basis: 100%` 设置:这确保了内部的项目整齐地推到按钮下方。在响应式断点上方,`.navbar-expand-lg .navbar-collapse` 的 `flex-basis: auto` 设置生效。
结论
关于 Bootstrap 4 中的导航栏还有很多要说的。然而,从本文中您应该记住的最重要的事情,并且将对您大有帮助的是
- `navbar` 是一个 `flexbox`,默认方向为 `row`
- `navbar-nav` 也是一个 `flexbox`,但默认方向为 `column`
- 因为 `navbar-nav` 通常用于 `navbar` 内部,所以它不会占用其父级的全部宽度,而只占用显示其内容所需的宽度。
- `navbar-expand-xx` 从响应式断点开始将 `navbar-nav` 的方向修改为 `row`。
- `navbar-collapse` 与 `collapse` 类一起使用,将元素设为 `flexbox` 容器并将其宽度设置为 100%,从而将其内容推到任何前任元素下方。
- `navbar-expand-xx` 修改 `navbar-collapse` 以使其宽度为 `auto`,从而仅占用其子元素所需的空间,并强制 `display: flex` 以使其在响应式断点之外可见。
参考文献
- 一篇关于 `flexbox` 系统的非常详尽的文章:理解 flexbox 您需要知道的一切
- 另一篇关于 `flexbox` 系统的指南:flexbox 指南
- 更多关于 `data` 属性的信息:HTML data-* 属性
- CSS 类的应用顺序称为“特异性”。您可以在这里阅读更多内容:关于 CSS 特异性的详细信息
历史记录
- 版本 1.0:初始版本
- 版本 1.0.1:添加源代码