《重学前端》笔记--CSS排版

543 阅读10分钟

本文为winter发布在极客时间的《重学前端》的学习笔记及一些补充。

大家支持正版喔:time.geekbang.org/column/arti…

导语

现在CSS提供了很多种排版方式,我们有很多选项可以选择自己适合的那一种,然而,正常流却是我们绕不开的一种排版。

如果我们从严苛的CSS标准角度去理解正常流,规定排版的算法,就需要引入上述那些复杂的概念。但是,如果我们单纯地从感性认知的层面去理解正常流,它其实是简单的。

盒模型(Box Model)

在讲解CSS排版前,需要了解下CSS盒模型。

当对一个文档进行布局(lay out)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)。CSS 决定这些盒子的大小、位置以及属性(例如颜色、背景、边框尺寸…)。

CSS盒模型本质上是一个盒子,封装周围的 HTML 元素,包括外边距(marign),边框(border),内边距(padding),内容(content)。

CSS盒模型有两种,W3C的标准盒模型和IE 盒模型(怪异盒模型):

  • W3C 标准盒模型:属性widthheight只包含content,不包括borderpadding
  • IE 盒模型:属性widthheight包含borderpadding,指的是content + padding + border

css3新增的box-sizing属性用于切换盒模型:

  • content-box 默认值,表示标准盒模型
  • border-box,表示IE盒模型

正常流

正常流的行为

我们可以用一句话来描述正常流的排版行为,那就是:依次排列,排不下了换行

在正常流的基础上,剩下的功能就是基础的延伸:

  • float float相关规则,使得一些盒占据了正常流需要的空间,我们可以把float理解为“文字环绕”。

  • vertical-align vertical-align相关规则规定了如何在垂直方向对齐盒,设置了盒与文字是如何混合排版的。

  • margin 我们可以把margin理解为“一个元素规定了自身周围至少需要的空间”。

正常流的原理

在CSS标准中,规定了如何排布每一个文字或者盒的算法,这个算法依赖一个排版的“当前状态”,CSS把这个当前状态称为“格式化上下文(formatting context,即FC)”。

Formatting context 是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。

我们可以认为排版过程是这样的:

格式化上下文 + 盒/文字 = 位置
formatting context + boxes/charater = positions

而我们需要排版的盒,分为块级盒和行内盒。所以排版规则了块级格式化上下文(Block Formatting Contexts,即BFC)行内级格式化上下文(Inline Formatting Contexts,即IFC),可以简单描述如下:

  • 块级格式化上下文顺次排列元素:

  • 行内级格式化上下文顺次排列元素:

当我们要把正常流中的一个盒或者文字排版,需要处理以下场景:

  1. 当遇到块级盒:排入块级格式化上下文。
  2. 当遇到行内级盒或者文字:首先尝试排入行内级格式化上下文,如果排不下,那么创建一个行盒,先将行盒排版(行盒是块级,由一行中所有的内联元素所组成,所以到第一种情况),行盒会创建一个行内级格式化上下文。
  3. 遇到float:把盒的顶部跟当前行内级上下文上边缘对齐,然后根据float的方向把盒的对应边缘对到块级格式化上下文的边缘,之后重排当前行盒。

当然,这仅仅是一个简单的逻辑,实际场景中CSS排版渲染会很复杂。

块级格式化上下文(BFC)

BFC是一个独立的布局环境,其中的元素布局是不受外界的影响。

创建方式

常见的BFC创建方式如下(更多的创建方式见这里):

  • 根元素(<html>),这里就可以理解正常流的逻辑
  • float的值不为none
  • overflow的值为autoscrollhidden
  • display的值为table-cell, table-caption, inline-block中的任何一个。
  • position的值不为relativestatic

布局规则

  • BFC内部的块盒与行盒,会在垂直方向,一个接一个地放置(正常流)
  • 属于同一个BFC的两个相邻盒的垂直方向上的margin会发生重叠,产生折叠边距
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • BFC的区域不会与float元素区域重叠(可由上一条推导出)
  • 每个元素的左外边距与包含块的左边界接触(从左到右),即使浮动元素也是如此。(说明BFC中子元素不会超出他的包含块)
  • 计算BFC的高度时,浮动元素也参与计算

BFC的作用

上面的布局规则描述可能有些地方有点抽象,下面简单描述下BFC规则的作用:

  • 避免外边距折叠
<div class="container">
    <div class="box1"></div>
    <div class="box2"></div>
</div>
.box1 {
  height: 20px;
  margin: 10px 0;
  background-color: green;
}

.box2 {
  height: 20px;
  margin: 20px 0;
  background-color: green;
}

根据第二条规则,这两个box会产生重叠,垂直方向上间距为20px(垂直距离取两个外边距的最大值)。

那么,根据第三条规则,我们可以将其中一个box放在另一个BFC中,来解决这个问题(当然,解决重叠问题还有其他方案,这里只记录BFC的解决方案)。

<div>
    <div class="wrapper">
        <div class="box1"></div>
    </div>
    <div class="box2"></div>
</div>
.wrapper {
    overflow: hidden;
}

  • 清除浮动 清除浮动,其实就是解决浮动元素的包含块高度塌陷的问题。

根据第五条规则,float元素会沿其容器的左侧或右侧放置。那么,在正常流中,float元素从网页的正常流动(文档流)中移除,会沿外部容器的左侧或右侧,从而导致其包含块高度塌陷。

<div class="container">
    <div class="box">浮动元素</div>
</div>
.container {
   border: 1px solid #000
}
.box {
  background-color: rgba(0,255,0,0.6);
  float: left;
}

所以,我们需要将浮动元素的包含块设置为BFC(也可以使用clear来解决)。

.container {
   border: 1px solid #000;
   overflow: hidden;
}

  • 自适应两栏布局 float元素会盖住下面的盒子,但是下面盒子里的文字反而还会环绕float元素。
<div class="left"></div>
<p>你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好
   你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好
   你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好
</p>
.left {
  float: left;
  width: 100px;
  height: 100px;
  background-color: blue;
}
p {
  background-color: green;
  width: 200px;
}

那么,我们根据第四条规则,可以将文字所在的盒设置为BFC,将两者隔开。这也是自适应布局的方案。

p {
  ...
  overflow: hidden;
}

而且,还可以通过设置float元素的margin-right或者padding-right实现间距效果(可以参考这篇文章)。

.left {
  ...
  margin-right: 10px;
}

行内级格式化上下文(IFC)

在一个行内格式化上下文中,盒是一个接一个水平放置的,从包含块的顶部开始。

  • 这些盒水平方向的marginborder以及padding属性会起作用
  • 这些盒在垂直方向上的对齐方式可以不一样:可以顶部或底部对齐,或根据其中文字的基线对齐
  • 行盒的宽度容纳不下子元素时,子元素会换到下一行显示,并且会产生新的行盒
  • IFC中时不可能有块级元素的,当插入块级元素时(如p中插入div)会产生两个匿名块与div分隔开,即产生两个IFC,每个IFC对外表现为块级元素,与div垂直排列

创建方式

常见的IFC创建方式如下:

  • 块级元素包含行内子元素:大多数块级元素(如<div>、<p>、<h1>-<h6>等)内部如果包含行内元素(如<span>、<a>、文本等),会自动形成IFC。例如,一个包含文本和图片的<p>标签,其内部的行内元素会在IFC中水平排列。
  • 行内块元素(inline-block)的影响:虽然行内块元素本身是块级盒子,但当它们被包含在块级容器中时,可能会与其他行内元素共同形成IFC。例如,多个<span style="display: inline-block;">元素在同一个块级父元素中会水平排列,此时父元素内部形成IFC。
  • 文本内容的自然包裹:即使块级元素中只有纯文本,没有其他行内元素,文本本身也会形成IFC。例如,一个空的<div>中添加文本,文本会在IFC中按行排列。

行盒(line box)

包含同一行的盒的矩形区域我们称之为行盒(line box)

  • 行盒的高度由line-height决定(有多个行内元素,则取line-height的最大值)。

行盒的高度能够容纳它包含的所有盒。当盒的高度小于行盒的高度时,盒的垂直对齐方式由vertical-align属性决定。

这里,我们可以理解为什么存在行内元素的上下间距不生效的问题。

  • 行盒的宽度由它的包含盒和float情况来决定。

通常,行盒的左边接触到其包含盒的左边,右边接触到其包含盒的右边。但是float元素优先排列,可能让行盒的宽度缩短。

<span class="s1">111111</span>
<span class="s2">222222</span>
<span class="s3">333333</span>
<span class="s4">444444</span>
<p>
<span class="s1" style="float: left">111111</span>
<span class="s2">222222</span>
<span class="s3">333333</span>
<span class="s4">444444</span>

由上面的例子我们可以看出,正常情况下盒之间有间距,但是存在float的情况下,盒会紧贴float元素,从而减少行盒的宽度。

并且,当一行的行内级盒的总宽度小于它们所在的行盒的宽度时,它们在行盒里的水平分布由text-align决定。

IFC的作用

  • 水平居中

当一个块元素要在环境中水平居中时,设置其为inline-block则会在外层产生IFC,通过text-align则可以使其水平居中。

  • 垂直居中

创建一个IFC,用其中一个元素撑开父元素的高度,然后设置其vertical-align:middle,其他行内元素则可以在此父元素下垂直居中。

总结

每一个盒的排版布局,依赖一个排版的“当前状态”,CSS称为“格式化上下文(formatting context,即FC)”。

盒分为块级盒和行内盒,所以排版规则了块级格式化上下文(Block Formatting Contexts,即BFC)行内级格式化上下文(Inline Formatting Contexts,即IFC)