框模型与视觉格式化模型

今天在了解BFC的相关知识时,看到了视觉格式化模型这个内容,似乎以前没有怎么见过,于是去查了下W3C规范,一查就把自己搞晕了,一下块框,一下块级框,还有块容器框,粗略的看了一遍真是没太理解,于是决定仔细的将它琢磨一下。

首先我们来回顾一下框模型,也就是常说的盒子模型,CSS框模型描述了为文档树中的元素而生成的矩形框。这些框会按照视觉格式化模型来排列显示。典型的框模型如下图所示:


可以看到有内容框,内边距框,边框框,外边距框四个框组成了框模型,大家应该对此都是比较熟悉的。此处提出几个需要注意的细节

  • margin应用于所有元素,但垂直外边距在非替换行内元素上不起作用,那么对于img、input之类的替换行内元素自然是起作用的。
  • 对于table系列,margin 只在 table 、caption上有效,对 tr、td、th等等无效。padding只在table,td,th有效。这条规则同样适用于display:table系列
  • marginpadding的百分数取值是相对于包含块的宽度,注意均是以宽度为参照。
  • margin允许负值,而且非常强大,具体可参见神奇的margin负值,但是padding是不允许负值的。
  • 外边距折叠,既然讲到这里,自己之前也写过相关的笔记,就顺便聊聊好了

外边距折叠(collapsed margin)

两个或多个 相邻(父子元素或兄弟元素)的块级框垂直方向上的 margin 会发生折叠。这种方式形成的外边距即可称为外边距折叠(collapsed margin),注意加粗的关键字。
其中折叠的计算方法为,如果两者的外边距都为正值,则折叠后的外边距值为两者的较大值;如果其中有一个是负值,则折叠后的外边距值为正值减去负值的绝对值得到的值;如果两者都是负值,则折叠后的外边距值为用0减去两者的绝对值中较大的那一个。

  1. 两个或多个
    单独的一个框是不会发生外边距折叠的,折叠是框与框间相互的行为,不存在 A 和 B 折叠,B 没有和 A 折叠的现象。

  2. 相邻(当且仅当下列情况发生,则称两个外边距相邻)

  • 双方都是同一个块格式化上下文(BFC)中属于正常排版的块级框。
  • 没有行框、没有间隙、没有内边距且没有边框隔开它们
  • 双方的框边缘垂直相邻,例如下列一种形式:
    • 框的上外边距和其属于正常排版的第一个孩子的上外边距(父子关系)demo
    • 属于正常排版的最后一个孩子的下外边距和其父亲的下外边距,但是只有在其父亲的高度计算值为‘auto’时才发生(父子关系)demo
    • 框的下外边距和其属于正常排版的下一个兄弟的上外边距(兄弟关系)demo
    • 框的上、下外边距,如果该框没有建立新的区块格式上下文,且‘min-height’计算值为零、‘height’计算值为零或‘auto’、且没有属于正常排版的孩子(自身关系),特别注意这一点demo

通过上面的叙述,其实我们可以从反面着手,就可以找出避免外边距折叠的方法

  1. margin 折叠只发生在块级元素上
  2. 创建了新块格式化上下文的元素(例如浮动的以及‘overflow’不是‘visible’的元素),不会与其属于正常排版的孩子出现外边距折叠。
  3. 有间隙或者内边距padding或者边框border隔开则不会发生外边距折叠
  4. 在浮动框和其他框之间的外边距不会折叠(甚至一个浮动框和其属于正常排版的孩子之间也不会出现外边距折叠)。
  5. 绝对定位框的外边距不会折叠(和其属于正常排版的孩子也不折叠)。
  6. 特殊:根元素的 margin 不与其它任何 margin 发生折叠

视觉格式化模型(visual formatting model)

CSS 视觉格式化模型(visual formatting model)是用来处理文档并将它显示在视觉媒体上的机制。视觉格式化模型中,每一个文档树中的元素根据框模型产生零个或多个框。

包含块Containing blocks
  • 一个元素,它的框的尺寸和位置会相对于一个特定的矩形框边缘来计算而得到,这个特定的矩形框称之为该元素的包含块。
  • 一个元素生成的框通常会充当其子框的包含块,我们称它为其子代“创建”(establishes)了包含块。我们平时说的元素的包含块,指的是该元素所处的包含块(比如说absolute元素的包含块指的是body或者最近定位父级),而不是指它自己产生的包含块。
  • 每个框会被给予一个相对于其包含块的位置,但它不会被局限在其包含块内;它有可能会溢出。

包含块的一些创建规则如下:

  1. 根元素的包含块是一个矩形称作初始包含块,初始包含块的宽度可用由根元素上的width属性指定,若width取值为auto,用户端提供初始宽度(如视口的当前宽度);初始包含块的高度可用由根元素上的height属性指定,若height取值为auto,包含块的高度将调整以适应文档内容;初始包含块不可以被定位或浮动。
  2. 如果元素的position值是relative 或者 static,包含块由最近的块级、单元格table-cell或者行内块inlin-block的祖先框的内容边界形成.
  3. 如果元素设置了position: fixed,包含块将由连续媒体的视口或分页媒体的页面区域创建.
  4. 如果元素设置了position: absolute,包含块由距离最近的,positionabsolute, relative 或者 fixed的祖先元素的padding框形成 ,如果没有这样的祖先元素,包含块是初始包含块.
块级元素和块框 Block-level elements and block boxes

块级元素Block-level elements

  • 是源文件中会被格式化成块状(例:段落)的元素。
  • display属性的以下取值会让一个元素成为块级元素:blocklist-itemtable
  • 块级元素视觉上呈现为块,竖直排列。

块元素Block elements

  • 块元素是 display 属性值为 block的元素,它应该是 块级元素 的一个子集,而不是等同的,一个 块元素 是一个 块级元素,但一个 块级元素 不一定是一个 块元素,所以不要混淆。

块级框Block-level box

  • 是参与块格式化上下文BFC的框。
  • 每个块级元素生成一个主要的块级框来包含其子框和生成的内容,同时任何定位方案都会与这个主要的框有关。
  • 某些块级元素还会在主要框之外产生额外的框:例如list-item元素。这些额外的框会相对于主要框来放置。
  • 块级框用于描述跟它父元素与兄弟元素之间的表现

块容器框Block container box

  • 除了table框,和可替换元素,一个块级框同时也是一个块容器框。
  • 一个块容器框要么只包含块级框,要么创建一个行格式化上下文IFC而只包含行内级框。
  • 并非所有的块容器框都是块级框,比如说不可替换的行内块(inline-block)和不可替换的table cell是块容器框但不是块级框,我的理解是inline-blocktable cell是可以包含块级元素的,但是本身并不是块级元素。所以是块容器框而不是块级框。
  • 块容器框用于描述跟它后代之间的影响

块框block box

  • 是块级框的块容器称作块框

匿名块框Anonymous block boxes

  • 块容器框要么只包含行内级框(inline box),要么只包含块级框(block-level box)。但通常情况下会包含两者,这个时候就会创建匿名块框,可以通过下面的例子来理解下

    1
    2
    3
    4
    <DIV>
    Some text
    <P>More text
    </DIV>

    上面的例子里面,看似既有inline box又有block-level box,实际上会有一个匿名块框围绕在”Some text”周围,这就使得DIV这个块容器框里面只包含块级框。所以如果一个块容器框(如上例中为DIV生成的框)有一个块级框(如上例中的P),那么我们强制它只包含块级框在其中。

  • 匿名框不能被选中,因此不能被设置样式,匿名框的继承属性会从包含它的非匿名框那里继承。匿名框的非继承属性将取其初始值。
  • 当一个行内框包含一个属于正常排版的块级框,这个行内框会被块级框(及其连续的块级兄弟)分离成两个框,它在块级框两边。在打断之前和打断之后的行框都附入匿名块框,并且该块级框与匿名块框成为兄弟。当这样的行内框受到相对定位影响,任何产生的移动同样影响到包含在行内框内的块级框。当一个元素导致了匿名块框的生成,则该元素上设置的属性一样能应用于该元素生成的框和其内容之上,举例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <body>
    <p>
    This is anonymous text before the SPAN。
    <span>This is the content of SPAN.</span>
    <span>This is the content of SPAN.</span>
    This is anonymous text after the SPAN。
    </p>
    </body>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    p{
    display: inline;
    border: 1px solid red;
    background: #fdb;
    position: relative;
    top: 50px;
    left:50px;
    }
    span{
    display: block;
    }

    p元素display:inline成为行内级元素,span元素display:block成为块级元素,p元素包含一段匿名文本(C1),随后是块级元素span,再随后是另一段匿名文本(C2)。
    对此生成了一个代表body的块框,它包含了围绕C1一个匿名块框、代表span的两个块框,和围绕C2的另一个匿名块框。围绕C1、C2的匿名块框与代表span的块框成为兄弟。
    而此时代表p元素的是什么框呢?结合前面说过的

    块容器框要么只包含行内级框(inline box),要么只包含块级框(block-level box)。但通常情况下会包含两者,这个时候就会创建匿名块框
    如果一个块容器框有一个块级框,那么我们强制它只包含块级框在其中。

    此时的p元素是一个行内级元素,那么生成的是行内级框,而此时代表body的块级框是一个块容器框,它包含了代表span的块级框,所以我猜想此时代表p元素的是匿名块框。下面有一点也可以证明。
    我们回到例子中,上述例子的结果如下所示:

    代表p的行内框设置了相对定位,由于任何产生的移动同样影响到包含在行内框内的块级框,所以span元素也产生了相同的移动;
    此时导致匿名块框生成的元素是p元素,我们给p元素加上了边框与背景,由于当一个元素导致了匿名块框的生成,则该元素上设置的属性一样能应用于该元素生成的框和其内容之上,所以边框和背景会显示成图上的形式
    demo

  • 当处理百分比值时,应该忽略匿名块框,而以其最近的非匿名祖先框来替代
    我们仍以上面的例子为基础,为body添加一个width:800px,为span添加一个padding: 2%,来看看结果会是怎么样
    上图中可以看到spanpadding值是16px,这个值也就是800px * 2% = 16px得来的,所以它直接忽略了它的父元素p,而以body替代,因为代表p的是匿名块框(这也证明了上面的猜想)。
    demo
行内级元素和行内框 Inline-level elements and inline boxes

行内级元素inline-level elements

  • 是源文档中那些不为其内容形成新的块;而让其内容分布在多行中的元素(如,段落内着重的文本,行内图形等等)。
  • 以下的display属性值产生一个行内级元素:inline, inline-table, and inline-block

行内级框inline-level box

  • 行内级元素生成行内级框,而这些框会参与某个行内格式化上下文IFC
  • 行内级框分为行内框和原子行内级框

行内框inline box

  • 内容会参与框本身的行内格式化上下文IFC的行内级框(inline-level box)称为行内框(inline box
  • 所有display:inline非替换元素生成的框是行内框(inline box

原子行内级框atomic inline-level box

  • 不是行内框的行内级框(例如可替换的行内级元素、行内块元素、行内表格元素)被称为原子行内级框
  • 它们是以单一不透明框Template:Extra not的形式来参与其行内格式化上下文IFC

匿名行内框Anonymous inline boxes

  • 任何被直接包含在一个块容器元素(而不是行内元素)中的文本必须视为匿名行内元素
  • 这样的行内框从其父块框那里继承可以继承的属性。非继承属性取其初始值,举例如下:

    1
    <P>Some <EM>emphasized</em> text</P>

    P元素生成一个块框,其内还有几个行内框。emphasized的框是由行内元素(EM)产生的一个行内框,而其它的框(“Some”和”text”)是块级元素(P)产生的行内框。后者就称为匿名行内框,因为它们没有与之相关的行内级元素。匿名行内框的颜色可以继承自P,而背景是初始的透明的。

行框Line box

  • 能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的行框。
  • 行框由行内格式化上下文IFC产生,用于表示一行
  • 更多详情可见IFC的相关部分
我们可以自己指定框的类型

框的类型并不是定死的,没有规定说p元素只能生成块框,比如说上面的一个例子中我们就使他成为了行内框,并由于一些原因最终成为了匿名框,display属性可以用来指定框的类型
diaplay属性有很多的可取值,下面讲讲他们之间的区别:

  1. block
    这个值让元素产生一个块框。
  2. inline-block
    这个值让元素产生一个行级块容器。行内块(inline-block)的内部会被当作块框来格式化,而此元素本身会被当作原子行级框来格式化。
  3. inline
    这个值让元素产生一个或多个的行内框。
  4. list-item
    这个值让元素(例:HTML 里的 LI)产生一个主块框与标记框。
  5. none

    • 这个值让元素不出现在排版结构中(也就是在视觉媒体中,元素既不产生框也不影响布局)。
    • 其子孙元素也不产生任何框:该元素及其内容会被从排版结构中完全移除。对子孙元素设定 display 属性不能覆盖这个结果。
    • 请注意display属性取值为 none 并不会产生隐形的框:none 完全不产生任何框。这是不同于visbility属性的。
  6. table、inline-table、table-row-group、table-column、table-column-group、table-header-group、table-footer-group、table-row、table-cell 与 table-caption
    这些值让元素产生表格元素的行为

再来看看定位方案`Positioning schemes

CSS 2.1 中,会根据三种定位方案来摆放框:

  • 正常排版Normal flow。CSS 2.1 中,正常排版包括对块级框的块格式化,对行级框的行格式化,对块级框和行级框的相对定位。
  • 浮动Float。在浮动模型中,一个框先按照正常排版来摆放,再将它从排版流中取出并尽可能地向左或向右偏移。其它内容可以排在一个浮动的周围。
  • 绝对定位Absolute positioning。在绝对定位模型中,一个框会从排版流中完全脱离出来(它对后续的兄弟没有影响),并相对其包含块来指定其位置。
正常排版Normal flow

框在正常排版中必然属于一个格式化上下文formatting contxet,要么是块格式化上下文Block formatting contxet ,要么是行格式化上下文Inline formatting context。块级框参与块格式化上下文。行级框参与行格式化上下文。
块格式化上下文BFC

  • BFC其实就是块级框的布局规则,相当于CSS页面布局的地基,并非什么神奇的概念,大家习以为常的一些东西其实最终的解释是可以归于BFC的。
  • 创建了BFC的元素规定了内部的块级框如何布局,并且使该框在页面上形成一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
  • 在BFC中,框会从包含块的顶部开始,一个接一个地,垂直向下地摆放
    我们常说block元素很霸道,会扩展到与父元素同宽,block元素会垂直排列,其实是因为BFC的约束。
  • 两个兄弟框之间的垂直距离由margin属性来决定。在同一个BFC中,相邻的块级框之间的垂直外边距会出现折叠。
    常见的外边距折叠,其实并不是bug,而是由于BFC的约束。
  • 在BFC中,每个框的左外边距边要紧贴其包含块的左边(对于从左往右的格式化),否则相反。即使在有浮动的情景下也是如此,除非框创建了一个新的BFC(在这种情况下该框可能会为了避开浮动框而变窄)。
  • BFC的区域不会与float box重叠(利用这个特性可以做自适应窗口大小
  • 计算BFC的高度时,浮动元素也参与计算(清除浮动的原理
    大家肯定知道overflow:hidden可以清浮动,哈哈,就是因为这个啦
  • 下列情况将创建一个BFC:
    • 根元素
    • 浮动 (元素的 float 不为 none)
    • 绝对定位元素 (元素的 position 为 absolute 或 fixed)
    • 行内块 inline-blocks (元素的 display: inline-block)
      注意:虽然display:inline-block的元素本身被当做一个原子行级框来格式化,但其内部会被当作块框来格式化,所以它是可以创建BFC的,而它本身是参与IFC的。你可以回到上面看看相关内容
    • 表格单元格 (元素的 display: table-cell,HTML表格单元格默认属性)
      注意:虽然display: table-cell的元素本身不能生成块级框,但是可以包含块框,所以可以创建BFC。你可以回到上面看看相关内容
    • 表格标题 (元素的 display: table-caption, HTML表格标题默认属性)
    • overflow 的值不为 visible的元素
    • 弹性盒子 flex boxes (元素的 display: flex 或 inline-flex)
  • BFC的相关应用可以见另一篇文章BFC的应用

行格式化上下文IFC

  • IFC其实就是行级框的布局规则,也不是很么神奇的概念。
  • 在IFC中,框会从包含块的顶部开始,一个接一个地水平摆放。
    这也就解释了inline元素与inline-block元素为啥是排在一排了
  • 摆放这些框的时候,它们在水平方向上的外边距、边框、内边距所占用的空间都会被考虑在内。
  • 在垂直方向上,这些框可能会以不同形式来对齐:它们可能会把底部或顶部对齐,也可能把其内部的文本基线对齐。
  • 以下情况会创建一个IFC:
    • 块容器框(block container box)中只有行内元素时会建立IFC
    • 匿名行内框(anonymouse inline boxes)中会建立IFC
  • 能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的行框
  • 行框的宽度是由包含块和存在的浮动来决定的。一般来说行宽的宽度就是包含块的宽度,行框的高度计算方法见行框高度计算
  • 行框一定会高到足以容纳它所包含的全部框。但它也可能比它所包含的最高框还要高(比如这些框是由基线对齐)
  • 当一个框的高度小于包含它的行框的高度,它在行框中垂直的位置由vertical-align属性来决定
  • 当几个行内级框在水平方向上无法塞进同一个行框时,他们会分布在两个或多个垂直堆放的行框中。行框会以既没有垂直间距也没有重叠的方式堆叠起来。
  • 当一个行内框的宽度超过了行框的宽度,则它会被分割成几个框,而这些框会分布在几个行框。如果此行内框不可分割(例如:单个字符、或语言指定的文字打断规则不允许在此行内框中出现打断、或该行内框受white-space属性为nowrap或pre的影响),那么该行内框溢出该行框。
  • 行内框被分割的时候,外边距、边框和内边距不会应用在出现分割的地方。
  • 通常,行框的左边紧贴其包含块的左边,而行框的右边紧贴其包含块的右边。然而,浮动框可以插在包含块边缘与行框边缘之间。因此,尽管在同一个IFC中的行框通常有同样的宽度(也就是其包含块的宽度),但它们的宽度也可能受浮动让水平可用空间减少的影响而有所改变。
  • 在同一个IFC中,行框的高度通常是变化的(例如:某一行包含了一个比较高的图片,而其它行只包含文本)。
  • 当一行上的行级框的总宽度小于包含它们的行框的宽度,则它们在行框内的水平分布由’text-align’属性来决定。
  • 行框如果满足以下条件,会以零高度来对待
    • 不包含文本或保留空格
    • 不包含内外边距或边框宽度非零的行内元素
    • 不包含其它正常排版的内容(images、inline-block、inline-table)
    • 不以保留的换行符结尾
  • 行框的高度的计算
    • 行框里各行内级框的高度,对于替换元素、行内块元素、行内表格元素来说,高度边界高度:对于行内框来说,这是由line-height决定。
    • 行框的高度是由该行框里面的最高的行内级框的高度决定的,而不是仅仅由最高的line-height决定。比如说,这个行框里面的文字的最高的line-height是20px,而这个行框里面有一个图片,它的高度是40px,那么这个行框的高度将是40px。

下面来一个实例:

1
2
3
4
<p>
Several <em>emphasized words</em> appear
<strong>in this</strong> sentence, dear.
</p>

P元素产生一个块框,它包含了五个行内框,其中的三个是匿名的。
为了格式化这一段,用户代理(通常为浏览器)将这五个框排入行框内。本例中,为P元素生成的框形成了行内框的包含块。如果该包含块足够宽,则这所有的行内框将放在一个行框内。如下所示

否则,行内框将被分割并分布在几个行框之内,如下所示

当存在行内框不可分割不可分割的情况时,会溢出,如下所示

浮动Float
  • 在当前行向左或右偏移的框就是浮动框。
  • 浮动框最有趣的特点是其它内容可以紧挨着它的一侧来排版 (也可以通过’clear’属性来禁止这样排版)。内容会紧挨着左浮框的右侧排版,而紧挨着右浮框的左侧排版。
  • 一个浮动框会向左或向右进行偏移直到其外边缘紧贴其包含块的边缘或另一个浮动框的外边缘。
  • 如果摆放浮动框时,当前行已有一个行框,则浮动框的顶部外边缘会与该行框的顶部对齐。
  • 如果因当前行剩余水平空间不足而放不下一个浮动框,则该浮动框会下移,直到找到放得下它的位置或者当前位置以下已没有更多浮动出现。
  • 因为浮动不属于正常排版流(Normal flow),在浮动框前面和后面被创建的非定位块框会如同该浮动不存在一样地垂直排版。但是,当前和随后的紧挨着浮动框被创建的行框,会按需要缩短其宽度来为浮动框的外边距框让出空间。
    这也就解释了为什么block元素会在浮动元素下面,而block元素中的inline元素会围绕着浮动元素但是不会被浮动元素盖住。
  • 如果被缩短后的行框过小,以至于它无法容纳任何内容,则应使该行框下移(并重新计算其可用宽度),直到它能容纳一些内容,或其下方已无其它浮动框。当前行中位于浮动框前面的任何内容要在浮动的另一边重新排版。左浮动就在其右边重新排版,右浮动就在其左边重新排版。
绝对定位 Absolute positioning
  • 在绝对定位模型中,一个框基于它的包含块而显式地偏移。它完全从正常排版中脱离(对后继的兄弟没有影响)。
  • 一个绝对定位框为它的正常排版子元素和绝对定位(不是fixed)后代生成一个新的包含块。
  • 绝对定位元素的内容不会在其它框的周围排列。它们可能会也可能不会挡住另外一个框的内容(或者被挡住),这取决于互相重合的框的堆叠层次。
  • 固定定位是绝对定位的一个子类。唯一的区别是,对于固定定位框,它的包含块由视口创建。
displaypositionfloat 的关系
  • 如果display值为none,元素不生成框,那么positionfloat无效。
  • 否则,如果position值为absolute或者fixed,那么此时float计算的值为none,也就是说并不会产生浮动,并且 display根据下面的表格进行设定。框的位置由top, right, bottomleft属性和包含块决定。
    所以如果inline元素的position值为absolute或者fixed,它就可以设置宽高了,因为display的计算值变为block了
  • 否则,如果float的值不是none,该框是浮动的,且display值根据下面的表格进行设定。
    所以如果inline元素设置了float,它就可以设置宽高了,因为display的计算值变为block了
  • 否则,如果元素是根元素,display值根据下面的表格进行设定,除了其在CSS2.1里面没有定义是否指定值list-item对应计算值block或者list-item
  • 否则,display 的计算值为指定的值。
    注意:此处的否则代表依次否定上一条,也就是说最后一条是排除了前面的所有条件的情况下。
分层的呈现 Layered presentation

主要是z-index属性,详见我想更了解z-index

结束语

写了这么多,虽然大部分是W3C规范里的内容,但是整个慢慢的看下来,自己也收获不小,文章里有一些自己的理解,因为经验尚且,可能会有错误,还请大家多多指教。里面有一些知识时穿插着的,所以建议先顺着看一遍理解一下,对于不懂的先放着,可能下文就有讲述,看完第一遍之后再看一遍以加深理解与印象。

参考资料