Skip to content
This page has been auto-translated and may contain errors.View in English

显示和正常流

docs.scrimba.com

在一个完全没有 CSS 的页面上放三个段落和几个链接,它们仍然会自己布局:段落从页面顶部向下堆叠,链接坐在你写的文本中。没有人告诉浏览器这样做。在你写任何布局规则之前,已经有一个布局了,你稍后使用的每种技术都是对它的改变,而不是从零开始。这一章讲的是这个起点和那个控制它的单一属性。

正常流

当浏览器在没有你自己的布局 CSS 的情况下布局页面时,它使用**正常流**:元素排列的默认方式。像段落和标题这样的块级元素从上到下堆叠,每个都在自己的行上。文本和链接在一行内从左到右运行,当空间用完时换到下一行。

你已经见过流动的元素:这一章是在 盒模型 的基础上构建的,因为是那些框在流动。正常流之所以值得命名,是因为布局中的其他一切都是相对于它定义的。Flexbox、网格和定位都是说"与默认值不同地排列这些"的方式,所以了解默认值就是让这些改变有意义的原因。

**正常流**是浏览器的默认布局:在任何 flex、grid、float 或定位进入之前你得到的排列。块框按源代码顺序垂直堆叠在页面上,每个都开始一个新行并占用可用的全部宽度。内联框沿着线从左到右流动,当当前行填满时换到下一行。

命名它的原因是布局工具被定义为对它的偏离。当你在容器上设置 display: flex 时,你告诉浏览器用 flex 规则而不是正常流来布局该容器的子元素。默认值永远不会消失;你只是将单个容器选择性地退出它。这也是为什么未设置样式的文档仍然可读的原因:正常流是一个真实的布局,而不是没有布局。

**正常流**是浏览器默认应用的布局模式,在浮动、定位、flex 或网格改变任何东西之前。它有两个协同工作的子模式:块布局,按源代码顺序垂直堆叠块级框,和内联布局,在一行内水平排列内联框并将它们包装到行框。在正常流中布局的块框占用全部可用宽度并开始一个新行;内联框只占用其内容所需的宽度并坐在当前行内。

精确命名很重要,因为整个布局系统是作为一组对它的替代方案构建的。display: flexdisplay: grid 替换容器的内部布局为 flex 或网格规则,而 position: absolutefloat 将框完全拉出正常流,使周围内容的布局就好像它不在那里一样。在每种情况下,正常流都是你偏离的基线,没有被明确地拉出它或交给另一个布局模式的框仍然在流动。这就是为什么没有样式表的文档以合理的顺序呈现的原因:块和内联流是默认值,而不是备用方案。

Juno正常流 正常流是你免费获得的布局:块向下堆叠在页面上,文本和链接沿着行运行。没有人打开它,它在你写任何 CSS 之前就已经存在。布局中的其他一切都是对这个默认值的改变,所以先想象一下默认值会有帮助。
Juno正常流 正常流是浏览器的默认值:块按源代码顺序从上到下堆叠,内联框沿着行运行并换行。Flex、网格和定位都是将容器选择性地退出它的方式,永远不是一次性全部替换。未设置样式的页面读起来很好,因为正常流是一个真实的布局,而不是没有布局。
Juno正常流 正常流是块布局垂直堆叠加上内联布局沿着行运行,在任何其他东西接触框之前默认应用。display: flexdisplay: grid 交换容器的内部布局,而 floatposition: absolute 将框拉出流,使兄弟元素忽略它。无论你不转向什么都留在流中,这就是为什么没有样式表的文档仍然按顺序呈现的原因。

块框和内联框

元素在正常流中有两种主要风格。**块框**占用其容器的整个宽度并在自己的行上堆叠,将下一个元素推向下方。段落 <p>、标题 <h1><div> 都是块,这就是为什么它们一个接一个地排列。

内联框是不同的。它坐在文本行中,只占用其内容所需的空间,所以几个可以共享一行。<span> 或链接 <a> 是内联的,这就是为什么句子内的链接停留在文字流中,而不是跳到自己的行上。

html
<p>这个段落是一个块,所以它自己占用一行。</p>
<p>阅读<a href="/css/box-model">盒模型</a>了解更多。</p>
<!-- 链接是内联的,所以它保持在句子内 -->

正常流中的每个框都被布局为块级或内联级,两者遵循不同的规则。**块框**开始一个新行,默认填充可用宽度,并垂直堆叠。<div><p><h1><section> 默认是块。内联框在一行内随着文本流动,只占用其内容的宽度,并坐在其邻近元素旁边,直到行换行。<span><a><strong> 默认是内联的。

让人困惑的区别是内联框忽略什么。内联框忽略 widthheight 和上下外边距。<span> 上设置 width: 200px 不会发生任何事,因为内联框由其内容决定大小,而不是由你决定。左右填充和外边距确实适用,但竖直间距不会像在块上那样将周围行推开。

css
.tag {
  width: 200px;        /* 被忽略:span 是内联的,由内容决定大小 */
  padding: 0 8px;      /* 水平填充有效 */
  margin-top: 40px;    /* 被忽略:内联框上没有竖直外边距 */
}

在正常流中,框是由块布局或内联布局根据其显示类型进行布局的,两者的区别超越了"堆叠或换行"。**块框**在自己的前后生成一个断点,消耗其包含块的全部内联大小(除非给定宽度),并遵守所有四个外边距和两个维度。内联框参与行框,由其内容决定大小,其布局由行高和基线对齐控制,而不是由 widthheight 控制。

精确的部分是内联框丢弃哪些属性。widthheight 不适用于非替换内联框,如 <span><a>,因为其大小来自其内容和周围行。上下外边距被计算但对行高没有影响,所以它们永远不会将相邻行推开,上下填充在视觉上呈现但不改变行框的高度,所以它可以与相邻行重叠。左右外边距和填充确实适用并移动内联邻近元素。替换内联元素如 <img> 是例外:它们携带内在维度,所以 widthheight 确实适用于它们,即使它们是内联级的。

css
.badge {              /* .badge 是 <span>,默认是内联的 */
  width: 120px;       /* 无效:width 不适用于内联框 */
  height: 40px;       /* 出于同样的原因无效 */
  padding: 12px;      /* 左/右移动邻近元素;上/下可以与行重叠 */
  margin: 8px 0;      /* 竖直的 8px 被忽略,水平的 0 会适用 */
}
Juno块框和内联框<p><div> 这样的块框占用全部宽度并在自己的行上堆叠。像 <span><a> 这样的内联框坐在一行内并只占用它们需要的空间。这就是为什么链接保持在你的句子内,而不是跳到自己的行上。
Juno块框和内联框 块框填充宽度并堆叠;内联框沿着行流动并调整大小以适应其内容。陷阱是内联框忽略 widthheight 和竖直外边距,所以在 <span> 上设置 width 不会做任何事。水平填充和外边距仍然有效,这就是为什么这会让人困惑。
Juno块框和内联框 内联框由其内容和其行框决定大小,所以 widthheight 不适用,竖直外边距和填充不会将相邻行推开。水平外边距和填充确实适用并移动邻近元素。例外是替换元素如 <img>,它们携带自己的维度,所以即使是内联时也接受 widthheight

显示属性

每个元素都有一个默认类型,块或内联,但你不必坚持它。display 属性让你改变元素的表现方式。在链接上设置 display: block 现在它占用自己的行;在列表项上设置 display: inline 它坐在一行上。

有第三个值可以解决一个常见问题:display: inline-block。它像内联框一样在行中流动,但它接受宽度、高度和竖直填充,就像块一样。这是你想要的,比如按钮或导航药片,应该坐在其邻近元素旁边但仍然占用一个设定的大小。

css
.nav-link {
  display: inline-block;   /* 在行中流动,但接受宽度和填充 */
  padding: 8px 16px;       /* 竖直填充现在有效,不像纯粹的内联 */
}

display 属性设置框如何布局,三个流值是 blockinlineinline-blockdisplay: block 使框开始自己的行并填充宽度,不管元素的默认是什么。display: inline 使它在一行内流动并丢弃 widthheight 和竖直外边距。两者都不改变元素的含义,只改变其布局。

inline-block 是有用的混合体:框像内联框一样在行中流动,所以几个并肩坐着,但它像块一样接受 widthheight 和竖直填充和外边距。当你想要在一行中的东西仍然需要真实的维度时,可以选择它,一组按钮或导航链接,而不需要转向 flexbox。当你需要用对齐和间距布置整个行或网格的项目时,flexbox 中的 display: flex 是更好的工具;inline-block 是用于较小情况的少数几个在一行中的大小框。

css
.stat {
  display: inline-block;   /* 与其兄弟元素坐在一行中 */
  width: 120px;            /* 但占用固定宽度,内联会忽略 */
  padding: 12px;           /* 完整填充适用,包括上下 */
}

display 选择框的布局模式,三个值将其保持在正常流中是 blockinlineinline-blockblock 给出在前后断开并填充其包含块的内联大小的框;inline 给出加入行框并丢弃 widthheight 和竖直外边距的框。设置 display 永远不会接触语义,只接触布局:一个设置为 display: inline<div> 对 DOM 和辅助技术仍然是 <div>,它只是内联布局。

inline-block 是刻意的混合体。框是内联级的,所以它参与行框并与内联邻近元素坐在一起,但其内部按块布局,所以 widthheight 和所有四个外边距和填充适用并占用真实的布局空间。这使其适合于一行上的少数几个大小框,你不想要 flex 容器,例如固定宽度的统计块或导航药片。注意事项是因为它是内联级的,你的 HTML 中两个 inline-block 元素之间的空格呈现为它们之间的真实空格,这在下面的高级部分涵盖。对于整行中的任何对齐、分配或换行,更倾向于 flexbox 中的 flex 布局;inline-block 是小众工具,而不是通用工具。

css
.pill {
  display: inline-block;   /* 内联级框,块级内部 */
  width: 96px;             /* 适用,因为内部按块布局 */
  padding: 10px 14px;      /* 所有四个边占用布局空间 */
  margin: 0 4px;           /* 竖直外边距现在也适用 */
}
Juno显示属性display 属性改变元素的表现方式是块还是内联框。block 占用自己的行,inline 坐在文本行中。方便的中间选项是 inline-block,它在行中流动但仍然接受宽度和填充,所以它是你想要的按钮和导航链接。
Juno显示属性display 设置布局,而不是含义:blockinline 或混合 inline-block。当你想要几个大小框坐在一行上时使用 inline-block,因为它在内联流动但接受 widthheight 和完整填充。对于需要对齐和间距的整行,改为选择 flexbox。
Juno显示属性display 选择布局模式,而不是语义,所以内联 <div> 对 DOM 仍然是 <div>inline-block 是具有块级内部的内联级框,所以它坐在一行中但遵守 widthheight 和所有四个外边距。为少数几个大小框保留它,并将真实行交给 flexbox,并注意它在相邻框之间留下的空格间隙。

隐藏东西:display none 对 visibility hidden

有时你需要一个元素不显示。有两种方式,它们的表现不同。display: none 完全移除元素:它消失并没有留下间隙,就好像它不在页面上一样。visibility: hidden 使其不可见但保留其空间,所以在它曾经所在的地方有一个空洞。

你想要哪一个取决于你是否想要取回空间。使用 display: none 来完全删除某个东西,比如关闭的菜单。当你想要隐藏某个东西而不让布局跳动时使用 visibility: hidden,因为其框仍然被保留。

css
.closed-menu {
  display: none;          /* 消失了,没有留下间隙 */
}
.placeholder {
  visibility: hidden;     /* 不可见,但仍然占用其空间 */
}

隐藏元素的两种方式不可互换。display: none 完全将元素拉出正常流:它不生成框,不占用空间,其周围的元素关闭,就好像它从未在那里一样。visibility: hidden 在流中保留框并保留其空间,所以布局不会移动;元素只是被绘制为不可见。

有一个辅助功能差异与视觉差异一样重要。一个带有 display: none 的元素也从辅助功能树中被移除,所以屏幕阅读器会跳过它,这对于根本不存在的内容是正确的,比如折叠的面板。visibility: hidden 也将内容从屏幕阅读器中隐藏,同时保留空间。如果你想要某个东西在视觉上消失但仍然被宣布给辅助技术,这两个都不是对的;这是视觉隐藏的实用程序类的工作,你可以在 CSS 如何工作 的语义中阅读它。

css
.tab-panel[hidden] {
  display: none;          /* 从流和辅助功能树中被移除 */
}
.spacer {
  visibility: hidden;     /* 保留其空间,仍然对屏幕阅读器隐藏 */
}

display: nonevisibility: hidden 都隐藏,但它们在流、框树和辅助功能中有所不同。display: none 根本不生成框:元素不存在于布局中,不占用空间,其后代无论其自己的 display 是什么都随它消失。visibility: hidden 正常生成框,所以它在流中占用其全部空间并仅抑制绘制;后代甚至可以设置 visibility: visible 在以其他方式隐藏的祖先内重新出现,这 display: none 永远不允许。

选择的辅助功能和行为后果是仔细选择的原因。display: nonevisibility: hidden 都从辅助功能树中移除元素,所以屏幕阅读器不会宣布任何一个。这使它们对于你想要在视觉上消失但仍然大声读出的内容是错误的,经典情况是"跳到内容"链接或仅屏幕阅读器标签;这些需要一个视觉隐藏的模式,将框剪辑到一个像素,同时在辅助功能树中保留它,而不是这里的任何属性。两个进一步的实际说明:display: none 不能以 opacityvisibility 可以的方式在状态之间转换或动画,一个设置为 display: none 的元素不是可聚焦的,其表单控件不被提交,而 visibility: hidden 控件也被跳过但其空间被保留。选择 display: none 来完全移除,visibility: hidden 来隐藏而保留布局静止,以及当辅助技术必须仍然到达它时的裁剪实用程序。

css
.dropdown[aria-expanded="false"] .menu {
  display: none;          /* 完全移除:没有框,没有空间,不可聚焦 */
}
.avatar-slot {
  visibility: hidden;     /* 框和空间被保留,绘制被抑制 */
}
Juno隐藏东西:display none 对 visibility hidden 两种隐藏方式:display: none 移除元素并没有留下间隙,visibility: hidden 使其不可见但保留其空间。当你也想要空间消失时选择 display: none,比如关闭的菜单。当你不想让布局跳动时选择 visibility: hidden
Juno隐藏东西:display none 对 visibility hiddendisplay: none 将元素拉出流,所以没有东西保留其空间;visibility: hidden 保留框和其空间,只隐藏绘制。两者也将元素从屏幕阅读器中隐藏,所以如果你想要某个东西在视觉上消失但仍然被宣布,两者都不是对的。那个工作属于视觉隐藏的实用程序类。
Juno隐藏东西:display none 对 visibility hiddendisplay: none 不生成框和不占用空间;visibility: hidden 保留框和空间并仅抑制绘制,子元素可以设置 visibility: visible 在隐藏的祖先内重新出现。两者都从辅助功能树中脱落,所以跳过链接或屏幕阅读器标签需要裁剪实用程序,而不是任何一个。记住 display: none 不可动画化,其控件不可聚焦或被提交。

显示如何改变内部布局

你已经看到 display 在块和内联之间切换元素。它做的不止这些。同一属性是将容器转变为 flexbox 或网格的东西,这是两个在行和列中排列子元素的现代工具。现在,要坚持的事情是 display 是决定框如何布局以及其子元素如何在其内部布局的一个拨号。

布局不是一个你翻转一次的大开关。你改变需要不同排列的特定容器上的 display,你留下的一切都停留在正常流中。flexbox定位 章节从这里继续。

display 实际上同时控制两件事:框本身如何在其兄弟之间坐着,以及框如何布局其自己的子元素。display: blockdisplay: inline 主要讲的是第一个。display: flexdisplay: grid 改变第二个:容器仍然作为块参与正常流,但在其内部,子元素由 flex 或网格规则而不是块和内联流进行布局。

这就是为什么移动到 flexbox 不会扔掉你关于流的所有知识。flex 容器在其父元素的正常流中仍然是块框;只有其子元素切换布局模式。正常流在其周围保持默认,你在需要它的特定容器上选择加入 flex 或网格。定位 章节涵盖了离开流的另一种方式,完全将框拉出它,而不是改变子元素如何排列。

display 值实际上是同时两种类型:一个外部类型,说框如何参与其父元素的布局,和一个内部类型,说框如何布局其自己的子元素。display: block 是外部块,内部流;display: flex 是外部块,内部 flex;display: inline-flex 是外部内联,内部 flex。那个两部分的模型是为什么 flex 容器仍然在正常流中作为块坐着,同时其子元素遵循 flex 规则的原因:你改变了内部类型,而不是外部类型。

一个更多的值在这里赚得一席:display: flow-root。它给予框一个正常的内部流,但强制其建立一个新的**块格式化上下文**(BFC:一个自包含的布局区域,其内部与外部不相互作用,所以其内部的浮动被容纳,其边缘内的外边距不会折叠)。在 flow-root 存在之前,人们用 overflow: hidden 或 clearfix 技巧触发 BFC 以容纳浮动的子元素;flow-root 是显式的、无副作用的说法"包含这个"。它也停止父子外边距折叠的描述在 盒模型 中。当你在 flexbox 中将容器移动到 display: flexdisplay: grid 时,你同样建立了一个新的格式化上下文,这是为什么内部外边距在现代布局中比浮动时代布局中表现得更可预测的部分原因。

css
.media {
  display: flow-root;     /* 包含任何浮动的子元素,没有 clearfix 技巧 */
}
.gallery {
  display: flex;          /* 正常流中的外部块,子元素的内部 flex */
}
Juno显示如何改变内部布局 同一个 display 属性,它在块和内联之间切换,也是将容器转变为排列子元素的 flexbox 或网格的东西。布局不是你翻转一次的一个开关;你改变需要它的容器上的 display,其余的保留在正常流中。flexbox 和定位章节从这里继续。
Juno显示如何改变内部布局display 控制框在其兄弟之间的坐法和它如何排列其自己的子元素。display: flexdisplay: grid 改变第二个,所以 flex 容器仍然是正常流中的块,同时其子元素切换到 flex 规则。这就是为什么学习 flexbox 建立在流之上而不是替换它。
Juno显示如何改变内部布局display 值携带外部类型,框如何加入其父元素,和内部类型,如何布局子元素,这就是为什么 flex 容器停留在流中作为块,同时其子元素去 flex。display: flow-root 保留正常内部流但开始一个新的块格式化上下文,所以它包含浮动并停止外边距折叠而不需要旧的 overflow 或 clearfix 技巧。Flex 和网格容器也建立了它们自己的上下文,这就是为什么外边距在它们内部表现得比在浮动时代布局中更可预测的原因。