深入理解文档流(Document Flow)和视觉格式化模型(CSS Visual Formatting Model)

理解基本的文档流我们就能知道 HTML 文档显示在 Web 的过程,当我们需要复杂的布局时,就不得不认真思考诸如’BFC’, ‘Stacking Context’等名词,了解清楚了,才能在处理布局上更加游刃有余。

最开始学习 CSS 的时候,我一度认为 CSS 基础很简单,但是 CSS 布局是一块难啃的骨头。又到后来,我去亚马逊买了畅销书HTML and CSS, design and build websites,书上内容浅显易懂,但是关于 CSS 布局,也就只有一章,而且似乎也没有提到复杂的文档流,我印象中也并不知道’BFC’, ‘Stacking Contexts’这些名词。

最近的复习中不断出现’负 margin’,‘BFC’,‘Stacking Contexts’等等概念,又在 MDN 上了解到 CSS Visual Formatting Model,我觉得有必要系统地梳理一遍思路

本文以 MDN Visual Formatting Model 这篇文章作为引导,并加入自己的理解和新增的知识点,不会涉及太多的布局真实场景,例如双飞翼布局,圣杯布局,多列等高布局等等,这里只作为基础

CSS 标准盒子模型是呈现每一个独立元素的基础,组合起来构成文档流

以下是一个标准的盒子,由 4 个部分组成

/document-flow-and-the-css-positioning/boxmodel.png

标准盒模型我们设置width属性的时候,width = content-width,怪异盒模型width = content-width + padding-width + border-width

当我们设置背景色或者背景图片时,默认会延伸到 border 外围,但是 Z-Ordering 的顺序在 border 以下,也就是说,如果同时设置了backgroundborder属性,border 区域还是显示 border,但是其Z-Order以下是background。我们可以使用background-clip属性改变这一默认行为,具体不再叙述

1
2
3
4
5
6
<p>test</p>
<p>test</p>
<a href="">test</a>
<a href="">test</a><br />
<a href="">test</a>
<a href="">test</a>
1
2
3
4
5
6
7
8
p,
a {
  width: 100px;
  height: 40px;
  border: 1px solid red;
  margin: 20px;
  padding: 30px;
}

/document-flow-and-the-css-positioning/inline-vs-block-box-model1.png
图一

/document-flow-and-the-css-positioning/inline-vs-block-box-model2.png
图二

仔细看上面两幅图,块级和行级元素盒子有以下几点不同

  1. 块级元素盒子会自动占满一行,行级元素盒子不会
  2. 块级元素可以设置宽高,行级元素宽高设置无效
  3. 块级元素之间上下 margin 有折叠,行级元素设置上下 margin 无效,但是左右 margin 有效,而且不会折叠
  4. 行级元素 content 部分出现在正常文档流中,设置上下 padding 会与其他元素发生重合,左右 padding 不会

仅仅了解完每个元素自己形成的盒子模型是不够的,因为自身的盒子在某些时候还会受到容器盒子(Containing Block)的影响,例如

  1. 基于百分比的值来设定元素盒子的width,height,margin,padding,这里需要注意,除了height是基于父元素盒子的height之外,其余三个属性都是基于父元素盒子的width
  2. 当设置为 Absolute Positioning 之后,基于百分比的值来设置盒子的偏移量,例如top,left等等,此时是基于父元素盒子border 内侧进行定位

关于容器盒子,我们最常认为它就是父元素的 content 部分,其实不然,有如下三种情况

  1. 如果子元素 position 属性为默认值static或者是relative,则其 Containing Block 为父元素的 content 部分
  2. 如果子元素 position 属性为absolute,则其 Containing Block 为第一个 position 不为static的父元素的 content+padding 部分,如果都是static,则为 Initial Containing Block

    Initial Containing Block

    The initial containing block has the dimensions of the viewport, and is also the block that contains the htmlelement. Simply put, the absolutely positioned element will be contained outside of the html element, and be positioned relative to the initial viewport.

    简而言之,就是视窗(Viewport)

  3. 如果子元素 position 属性为fixed,则其 Containing Block 为视窗(Viewport)

知道标准盒子模型以及容器盒子模型后,需要用一定的规则将这些盒子“组装”起来,以下我们来看看“组装”方式

一旦盒子模型创建成功,接下来便是将这些盒子按照一定规则组装起来,默认的组装规则就是 Normal Flow

使用position: relative或者默认的position: static,且没有设定浮动,此时元素就是在 Normal Flow 中

Normal Flow 中,块级元素盒子垂直方向一个挨着一个,行级元素盒子水平方向一个挨着一个,一行不够之后换到下一行

如果使用position: relative,可以将盒子基于原来的位置,通过设定top,bottom,left,right进行位移

设置一个元素为浮动之后,这个元素盒子在一行中移动到最左边或者最右边,并且脱离 Normal Flow

浮动元素对其后面的元素或者是其父元素都会产生影响,除非其后元素使用clear属性清除这一影响,我之前写过一篇从圣杯和双飞翼看浮动流的文章,里面详细解释了浮动流的过程

在 Floats 中,虽然盒子脱离了文档流,但是其对于之后的元素盒子或者是父元素盒子都会产生影响,除非影响被清除。但是在Absolute Positioning Scheme中,盒子可以说完全抽离 Normal Flow,且不再对其他元素产生影响

此时元素盒子的位置完全基于其容器盒子(Containing Block),主要两种情况

  1. 如果绝对定位使用position: absolute,容器盒子为第一个 position 不为static的父元素的 content+padding 部分
  2. 如果绝对定位使用position: fixed,容器盒子为视窗(Viewport)

前面提到的都是元素应该出现在文档中的位置,但是如果发生堆叠,那么显示顺序(Z-Ordering, or Stacking Ordering)是怎么样的呢?

HTML 文档默认显示给我们的是第 0 层,这也是z-index的默认值,为 0

  • bottom layer (farthest from the observer)
  • Layer -3
  • Layer -2
  • Layer -1
  • Layer 0 (default rendering layer)
  • Layer 1
  • Layer 2
  • Layer 3
  • top layer (closest to the observer)

正常的堆叠顺序,遵循如下几个规则(顺序由底向上,如果发生重叠,只有最上的元素能被用户看到)

  1. 根元素(<html>)的backgroundborder,在堆底
  2. 没有被定位的元素(position: static),根据在 HTML 文档出现顺序,后面出现的在堆顶
  3. 被定位的元素(除了static),根据在 HTML 文档出现顺序,后面出现的在堆顶
  4. 如果使用display: flex,且对 flex container 下的子元素使用order属性改变顺序,也会影响到堆叠顺序,order最高的元素出现在堆顶

依据上面的规则,我们看一个例子

/document-flow-and-the-css-positioning/z-ordering-1.png
5 个 DIV 在文档的出现顺序为 1,2,3,4,5

首先来看看z-index的定义

The z-index CSS property specifies the z-order of a positioned element and its descendants or flex items (children of an element with display: flex). When elements overlap, z-order determines which one covers the other. An element with a larger z-index generally covers an element with a lower one.

也就是说,z-index只能在 flex-item 或者 positioned-items 下设置才有效,否则无效

再看一个例子(在上一个例子基础上使用z-index

/document-flow-and-the-css-positioning/z-ordering-2.png
5 个 DIV 在文档的出现顺序为 1,2,3,4,5

其实我们使用z-index,实际上是形成了一个Stacking Context,不同的Stacking Context下的元素层叠顺序互不影响,它们之间的层叠顺序由上层元素之间的层叠顺序决定

创建Stacking Context有如下几种常见方法

  1. HTML 文档根元素<html>创建了默认的Stacking Context
  2. 设置了position: fixed或者absolute的元素,且z-index值不为默认值auto
  3. 设置了display: flexcontainer 下的子元素,且z-index值不为默认值auto
  4. 设置opacity值小于 1 的元素

前面提到过,不同的 Stacking Context 下的元素层叠顺序互不影响,它们之间的层叠顺序由上层元素之间的层叠顺序决定

z-index的 initial(默认值)为auto

auto

The box does not establish a new local stacking context. The stack level of the generated box in the current stacking context is the same as its parent’s box.

也就是说,如果一个元素没有创建Stacking Context,则其顺序由父级Stacking Context决定,最顶层的Stacking Context为根元素<html>创建,其 z-index 值为 0

还是先看一个例子,下面分别是文档结构和结果图

Root

  • DIV #1
  • DIV #2
  • DIV #3
    • DIV #4
    • DIV #5
    • DIV #6

/document-flow-and-the-css-positioning/z-ordering-3.png

到这里 Document Flow 和 CSS Formatting Model 也就结束了,涵盖了基本的布局规则。只有深入理解这些基础,才能更好地去理解以前的一些布局 Trick,比如双飞翼布局,圣杯布局等等,这些布局建立在这些基础上,如果理解了这些内容,自己实现一些布局 Trick 也不是不可能,虽然现在 Flex 布局已经可以解决很多问题了

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/Visual_formatting_model
  2. https://developer.mozilla.org/en-US/docs/Web/CSS/z-index

相关内容