大漠 2018-08-07 23:28
最近在纠结程序语言和设计语言中的一些概念,整到层叠上下文和图层相关的事情,然后发现自己对于CSS中的层叠相关的知识并没有自己想象中那样理解的透彻。因此花了一段时间重新梳理了一下相关的知识。
如果想要理解清楚CSS中的层叠相关的知识点,我们就很有必要先了解一些重要的概念:
- 文档流(Normal Flow)
- 格式化上下文(Formatting Context)
- 层叠上下文(Stacking Context)
- 层叠水平(Stacking Level)
- 层叠顺序(Stacking Order)
文档流
在CSS中,文档流是一个很基础也是很重要的一个概念。很多时候她被称为Document Flow,但在CSS的标准被称为Normal Flow,即普通流或常规流。大家更喜欢称之为文档流。那么CSS的文档流是怎么一回事呢?
在HTML中任何一个元素其实就是一个对象,也是一个盒子。在默认情况下它是按照出现的先后顺序来排列,而这个排列的顺序就是文档流。
文档流是元素在Web页面上的一种呈现方式。所有的HTML元素都是块盒子(Block Boxes,块级元素)或行内框(Inline Boxes,行内元素)。当浏览器开始渲染HTML文档时,它从窗口的顶端开始,经过整个文档内容的过程中,分配元素需要的空间。除非文档的尺寸被CSS规则限定,否则浏览器垂直扩展文档来容纳全部的内容。每个新的块级元素渲染为新行。行内元素则按照顺序被水平渲染直到当前行遇到边界,然后换到下一行垂直渲染。
如果你读过CSS相关的规范,不难发现这样的过程包括了块格式化(BFC:Block formatting context)、行内格式化(IFC:Inline formatting context)、相对定位(Relative positioning)和Run-in Boxes的定位。
事实上,在普通文档流中的盒子属于一种格式化上下文(Formatting Context),大家较为熟悉的就是块格式化上下文(Block formatting context)和行内格式化上下文(Inline formatting context)。不过有一点面要注意,它们只能是其中一者,但不能同时属于两者。言外之意,任何被渲染的HTML元素都是一个盒子(Box),这些盒子不是块盒子就是行内盒子。即使是未被任何元素包裹的文本,根据不同的情况,也会属于匿名的块盒子或行内盒子。
综合上面的描述,也可以理解格式化上下文对元素盒子做了一定的范围的限制,其实就是类似有一个width
和height
做了限制一样。如果从这方面来理解的话,普通流就是这样的一个过程:
- 在对应的块格式化上下文中,块级元素按照其在HTML源码中出现的顺序,在其容器盒子里从左上角开始,从上到下垂直地依次分配空间层叠(Stack),并且独占一行,边界紧贴父盒子边缘。两相邻元素间的距离由
margin
属性决定,在同一个块格式化上下文中的垂直边界将被重叠(Collapse margins)。除非创建一个新的块格式化上下文,否则块级元素的宽度不受浮动元素的影响。 - 在对应的行内格式化上下文中,行内元素从容器的顶端开始,一个接一个地水平排列。
扯了这么多,如果简单的描述就是:如何排列HTML元素而已。拿个块格式化上下文的普通文档流来举例,就像下面这样:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
对应的效果如下:
上例中看到的文档流就是一个普通的文档流,也是一个正常的普通文档流。
在CSS中也可以通过float
或者position:absolute
两种方法让元素脱离文档流。而这两者的表现实际上非常相似。简单的可以理解为部分无视和完全无视的区别:
使用
float
脱离文档流时,其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在周围(可以说是部分无视)。而对于使用position:absolute
脱离文档流的元素,其他盒子与其他盒子内的文本都会无视它(可以说是完全无视)。
格式化上下文
在介绍文档流的一节中,多次提到了格式化上下文这个概念。那么格式化上下文指的又是什么呢?
格式化上下文指的是初始元素定义的环境。
其主要包含两个要点,一个是元素定义的环境,另一个是初始化。
在CSS中,元素定义的环境有两种,也就是前面提到:块格式化上下文和行内格式化上下文。这两种上下文定义了在CSS中元素所处的环境,格式化则表明了在这个环境中,元素处理此环境中应当被初始化。用一句话来描述就是:
元素在此环境中应当如何排版布局等。
块格式化上下文其实也是大家常常称为的BFC,指的是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。
下列方式会创建块格式化上下文:
- 根元素或包含根元素的元素
- 浮动元素(元素的
float
不是none
) - 绝对定位元素(元素的
position
为absolute
或fixed
) - 行内块元素(元素的
display
为inline-block
) - 表格单元格(元素的
display
为table-cell
,HTML表格单元格默认为该值) - 表格标题(元素的
display
为table-caption
,HTML表格标题默认为该值) - 匿名表格单元格元素(元素的
display
为table
、table-row
、table-row-group
、table-header-group
、table-footer-group
(分别是HTMLtable
、row
、tbody
、thead
、tfoot
的默认属性)或inline-table
) overflow
值不为visible
的块元素display
值为flow-root
的元素contain
值为layout
、content
或strict
的元素- 弹性元素(
display
为flex
或inline-flex
元素的直接子元素) - 网格元素(
display
为grid
或inline-grid
元素的直接子元素) - 多列容器(元素的
column-count
或column-width
不为auto
,包括column-count
为1
) column-span
为all
的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)。
创建了块格式化上下文的元素中的所有内容都会被包含到该BFC中。
BFC是一个比较抽象的概念。如果要彻底的讲述清楚,那么可以用几篇的篇幅来阐述,如果你想深纠的话,建议你花一些时间阅读以下这些文章:
- BFC相关教程 @w3cplus
- Understanding Block Formatting Contexts in CSS
- 关于CSS-BFC深入理解
- CSS:BFC 最熟悉的陌生人
- CSS之BFC详解
- CSS: The block formatting context
- Block Formatting Contexts
- CSS 盒模型、解决方案、BFC 原理讲解
- CSS深入理解流体特性和BFC特性下多栏自适应布局
- 关于对CSS中BFC的理解
相对于块格式化上下文,在行内格式化上下文中,盒子( Boxes )一个接一个地水平排列,起点是包含块的顶部。 水平方向上的 margin
,border
和 padding
在盒子之间得到保留。 盒子在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐。 包含那些框的长方形区域,会形成一行,叫做行框。
在CSS中,对于行框这样的东东涉及的页就更多了。这里不做过多的阐述。感兴趣的同学,自己可以阅读相关规范深究。
三维空间
平时我们从设备终端看到的HTML文档都是一个平面的,事实上HTML文档中的元素却是存在于三个维度中。除了大家熟悉的平面画布中的x
轴和y
轴,还有控制第三维度的z
轴。
其中x
轴通常用来表示水平位置,y
轴来表示垂直位置,z
轴表示屏幕内外方向上的位置。
对于x
和y
轴我们很易于理解,一个向右,一个向下。但对于z
轴,理解起来就较为费力。在CSS中要确定沿着z
轴排列元素,表示的是用户与屏幕的这条看不见的垂直线:
从正常流的一节中我们可以知道,如果元素不脱离文档流,或者不通过其他CSS的规则来改变初始化的格式化上下文环境,元素盒子是不可能会有层叠在一起的。但我们使用float
或position:absolute
时可以让元素脱离文档流。那么问题来了:
- 当一个设置了
z-index
值的定位元素与常规文档流中的元素相互重叠的时候,谁会被置于上方? - 当定位元素与浮动元素相互重叠的时候,谁会被置于上方?
- 当定位元素被嵌套在其他定位元素中时会发生什么?
要回答这些问题,我们需要进一步地理解z-index
是如何工作的,尤其是层叠上下文,以及层叠次序这些概念。
层叠上下文
上一节提到过,网页及其每个元素都有一个坐标系统。该系统包括一个三维z
轴,其中的元素是层叠(Stacked)的。z
轴的方向指向查看者,x
轴指向屏幕的右边,y
轴指向屏幕的底部。
通常,浏览器会按照CSS规范中指定的特定顺序放置元素:
在DOM树中最先出现的元素被放在首位,之后出现的元素被放在前面的元素之上。但它并不总是那么简单。只有当页面上的所有元素是自然流才起作用。也就是说,当没有元素在流中的位置被改变或者已经脱离文档流,才起作用。
CSS中有两种方式影响元素的流和位置的方法:
- 使用
position
属性定位元素。除了默认的static
值外的元素被称为定位元素 - 通过使用
float
属性浮动元素来改变元素的流
事实上,每个HTML元素都属于一个层叠上下文。给定层叠上下文中的每个定位元素都具有一个整数的层叠层级,具有更大堆栈级别的元素盒子总是在具有较低堆栈级别的盒子的前面(上面)。盒子可能具有负层叠级别。层叠上下文中具有相同堆栈级别的框根据文档树出现的顺序层叠在一起。
文档中的层叠上下文由满足以下任意一个条件的元素形成:
- 根元素 (HTML)
z-index
值不为auto
的 绝对/相对定位position
值为fixed
或sticky
- 一个
z-index
值不为auto
的 Flex 项目 (Flex item),即:父元素display: flex|inline-flex
opacity
属性值小于1
的元素transform
属性值不为none
的元素mix-blend-mode
属性值不为normal
的元素filter
、perspective
、clip-path
、mask
、mask-image
、mask-border
、motion-path
值不为none
的元素perspective
值不为none
的元素isolation
属性被设置为isolate
的元素- 在
will-change
中指定了任意 CSS 属性,即便你没有直接指定这些属性的值 -webkit-overflow-scrolling
属性被设置touch
的元素
而且每个网页都有一个默认的层叠上下文。这个层叠上下文的根源就是html
元素。html
元素中的一切都被置于这个默认的层叠上下文的一个层叠层上。理解起来有点怪。那么先来看一个图:
层叠上下文1 (Stacking Context 1)是由文档根元素形成的。 层叠上下文2和3 (Stacking Context 2, 3) 都是层叠上下文1 (Stacking Context 1) 上的层叠层。 他们各自也