# CSS排版:从毕升开始,我们就开始用正常流了 你好,我是winter。今天我们来聊聊CSS的正常流。 我想,在CSS中,大家最讨厌的大概就是排版部分了。因为早年的CSS设计上不能够很好地支持软件排版需求,导致大家需要使用很多黑科技,让很多新人望而却步。 现在CSS提供了很多种排版方式,我们有很多选项可以选择自己适合的那一种,然而,正常流却是我们绕不开的一种排版。 我们能够在网上看到关于正常流的各种资料,比如:块级格式化上下文、margin折叠等等……这一系列的概念光是听起来就令人非常头痛。 所以我相信很多同学一定会奇怪:正常流到底正常在哪里。事实上,我认为正常流本身是简单和符合直觉的东西。 我们之所以会觉得它奇怪,是因为如果我们从严苛的CSS标准角度去理解正常流,规定排版的算法,就需要引入上述那些复杂的概念。但是,如果我们单纯地从感性认知的层面去理解正常流,它其实是简单的。 下面,就让我们先抛弃掉所有的已知概念,从感性认知的角度出发,一起去理解一下正常流。 ## 正常流的行为 首先,我们先从词源来讲一讲排版这件事。 在毕昇发明活字印刷之前,排版这项工作是不存在的,相应的操作叫做“雕版”。人们要想印刷书籍,就需要依靠雕版工人去手工雕刻印版。 活字印刷的出现,将排版这个词引入进来,排版是活字印刷的15道工序之一,不论是古代的木质活字印刷,还是近代的铅质活字印刷,排版的过程是由排版工人一个字一个字从字架捡出,再排入版框中。实际上,这个过程就是一个流式处理的过程。 从古代活字印刷开始,到现代的出版行业,再到今天的Web,排版过程其实并没有什么本质的变化,只不过,今天在我们的CSS中,排版需要处理的内容,不再是简单的大小相同的木字或者铅字,而是有着不同字体和字号的富文本,以及插入在富文本中大小不等的盒。 并且,在这些过程中,都会有一个正常流的存在。那么,正常流是什么样的呢? **我们可以用一句话来描述正常流的排版行为,那就是:依次排列,排不下了换行。**这个操作很简单吧,我想,任何一个不懂排版的人都会将其作为排版时的第一反应。 理解了正常流的基本概念,剩下的功能只需要在它的基础上延伸一下就好。 在正常流基础上,我们有float相关规则,使得一些盒占据了正常流需要的空间,我们可以把float理解为“文字环绕”。 ![](https://static001.geekbang.org/resource/image/af/65/aff7250eac6064158021aea86dd4ac65.png) 我们还有vertical-align相关规则规定了如何在垂直方向对齐盒。vertical-align相关规则看起来复杂,但是实际上,基线、文字顶/底、行顶/底都是我们正常书写文字时需要用到的概念,只是我们平时不一定会总结它们。 下图展示了在不同的vertical-align设置时,盒与文字是如何混合排版的。为了方便你理解,我们用代码给大家标注了基线、文字顶/底、行顶/底等概念。 ![](https://static001.geekbang.org/resource/image/aa/e3/aa6611b00f71f606493f165294410ee3.png) (点击大图查看) 除此之外,margin折叠是很多人非常不理解的一种设计,但是实际上我们可以把margin理解为“一个元素规定了自身周围至少需要的空间”,这样,我们就非常容易理解为什么margin需要折叠了。 ## 正常流的原理 我们前面描述了正常流的行为,接下来我们要切换一下模式,用比较严谨的姿势来理解一下正常流。 在CSS标准中,规定了如何排布每一个文字或者盒的算法,这个算法依赖一个排版的“当前状态”,CSS把这个当前状态称为“格式化上下文(formatting context)”。 我们可以认为排版过程是这样的: > 格式化上下文 + 盒/文字 = 位置 > formatting context + boxes/charater = positions 我们需要排版的盒,是分为块级盒和行内级盒的,所以排版需要分别为它们规定了块级格式化上下文和行内级格式化上下文。 与正常流一样,如果我们单纯地看格式化上下文,规则其实是非常简单的。 块级格式化上下文顺次排列元素: ![](https://static001.geekbang.org/resource/image/a5/e7/a5e1b9a77d9745499f96d25cf0a0dbe7.png) 行内级格式化上下文顺次排列元素: ![](https://static001.geekbang.org/resource/image/1c/cf/1ced4fa809b30343df45e559cf0c08cf.png) 注意,块级和行内级元素的排版,受文字书写方向的影响,这里我们讲上下左右只是为了方便你直观理解。 当我们要把正常流中的一个盒或者文字排版,需要分成三种情况处理。 * **当遇到块级盒**:排入块级格式化上下文。 * **当遇到行内级盒或者文字**:首先尝试排入行内级格式化上下文,如果排不下,那么创建一个行盒,先将行盒排版(行盒是块级,所以到第一种情况),行盒会创建一个行内级格式化上下文。 * **遇到float盒**:把盒的顶部跟当前行内级上下文上边缘对齐,然后根据float的方向把盒的对应边缘对到块级格式化上下文的边缘,之后重排当前行盒。 我们以上讲的都是一个块级格式化上下文中的排版规则,实际上,页面中的布局没有那么简单,一些元素会在其内部创建新的块级格式化上下文,这些元素有: 1. 浮动元素; 2. 绝对定位元素; 3. 非块级但仍能包含块级元素的容器(如inline-blocks, table-cells, table-captions); 4. 块级的能包含块级元素的容器,且属性overflow不为visible。 这里的最后一条比较绕,实际上,我个人喜欢用另一种思路去理解它: 自身为块级,且overflow为visible的块级元素容器,它的块级格式化上下文和外部的块级格式化上下文发生了融合,也就是说,如果不考虑盒模型相关的属性,这样的元素从排版的角度就好像根本不存在。 好了,到这里我们已经讲完了正常流的排版详细规则,但是理解规则仅仅是基础,我们还需要掌握一些技巧。 ## 正常流的使用技巧 现在,我们就一起来动手用实际的例子来研究一下。我们今天来看看等分布局和自适应宽,从这两种经典布局问题入手,一起来探索一下正常流的使用技巧。 ### 等分布局问题 横向等分布局是一个很常见的需求,按照一般的思路,我们可以使用百分比宽度来解决,我们参考以下代码: ```