结合CSS2.1规范看BFC和Positioning

Posted by Yeoman on 2018-06-07

1. 写在前面

做前端开发已经有一段时间了,想着可以开始对一些CSS知识做一些总结了。CSS2.1中,我认为最基础也最重要的概念,莫过于定位和布局了,CSS已经给人一种比较玄学的感觉,本文尝试结合W3C规范中的一些重要概念来做一些总结。

2. CSS控制盒

在讲CSS的定位之前,需要先了解CSS的两种基本元素,Block-level elements-块级元素Inline-level elements-内联元素

2.1 块级元素和块盒

规范中有很多晦涩的描述,我们只看比较重要的部分。

Block-level elements are those elements of the source document that are formatted visually as blocks (e.g., paragraphs). The following values of the ‘display’ property make an element block-level: ‘block’, ‘list-item’, and ‘table’.

Block-level boxes are boxes that participate in a block formatting context.

'display'设置成这三种值 'block', 'list-item', and 'table',可以把一个元素变成块元素,同时参与BFC(block formatting context)。

2.2 内联元素和内联盒

The following values of the ‘display’ property make an element inline-level: ‘inline’, ‘inline-table’, and ‘inline-block’. Inline-level elements generate inline-level boxes, which are boxes that participate in an inline formatting context.

An inline box is one that is both inline-level and whose contents participate in its containing inline formatting context.

'display'设置成这三种值 'inline', 'inline-table', and 'inline-block',可以把一个元素变成行内元素,同时参与IFC(inline formatting context)。

3. CSS的display属性

我们知道display属性可以决定块的类型,总结下display的各个属性和作用:

display 描述
none 当设置了这个属性之后,不会生成一个不可见的盒子,而是根本不会生成任何盒子。
block 生成一个块级盒子
inline 生成一个或多个内联盒
inline-block 生成一个inline-level block container,盒子内部会被格式化成一个块级盒,本身则被格式化成内联盒
list-item 像li标签这样的会生成一个principal block box -主块级盒和一个marker box-标记盒
table类 会让一个元素表现的像表格元素
块级元素的特性
  • 块级元素会占用一行,并尽可能占据整行。
  • 可以设置width/height/margin/padding属性。
  • 当两个相邻的块级元素都有margin属性的时候,会发生margin collapse。
内联元素的特性
  • 在同一行上从左向右排列,但是当几个内联元素换行之后,会存在间隙问题。
  • 设置width/height无效,可以通过设置display为inline-block来解决这个需求。
  • 只能设置水平方向的margin和padding, 垂直方向设置无效。

4. CSS定位方案(Positioning schemes)

在CSS2.1中,一个盒子有三种定位方案:

  • Normal flow(普通流),其中包含了块格式化的块级盒子;行内格式化的行内盒子;以及采用相对定位的块级或者行内盒子。
    • 在一个块格式化上下文中,盒在竖直方向一个接一个地放置,从包含块的顶部开始。两个兄弟盒之间的竖直距离由’margin’属性决定。同一个块格式化上下文中的相邻块级盒之间的竖直margin会发生崩塌(合并)。
    • 在一个内联格式化上下文中,盒在水平方向上一个接一个放置,从包含块的顶部开始。这些盒之间的水平margin,border和padding都有效。盒可能以不同的方式垂直对齐:以它们的底部或者顶部或者以文本的baselines(基线)对齐。包含来自同一行的盒的矩形区域叫做line box
  • Float(浮动),在浮动模型中,盒子会先根据普通流来排版,然后「out of the flow」,尽可能地往左右偏移。其他内容可能会延着浮动盒的一侧排列。
  • Absolute positioning(绝对定位),在绝对定位模型中,盒子会从普通流中完全移除「out of the flow」然后根据它的包含块来确定位置。

4.1 如何确定包含块

positioning定位的元素会根据他的包含块来进行定位,那么元素的包含块是如何确定的呢?

  1. 如果 position 属性为 static 或 relative ,包含块就是由它的最近的祖先块元素(比如说inline-block, block 或 list-item元素)或格式化上下文(比如说 a table container, flex container, grid container, or the block container itself)的内容区的边缘组成的。
  2. 如果 position 属性为 absolute ,包含块就是由它的最近的 position 的值不是 static (也就是值为fixed, absolute, relative 或 sticky)的祖先元素的内边距区的边缘组成。
  3. 如果 position 属性是 fixed,包含块就是由 viewport (in the case of continuous media) 或是组成的。

4.2 CSS的position属性

一个盒可能会根据三种定位方案来布局:

position 定位机制
static 盒子根据Normal flow进行排列,’top’,’right’,’bottom’,’left’属性设置无效。
relative 盒子A的位置根据普通流来计算,如果设置了偏移属性,则根据盒子A在普通流的默认位置进行偏移。如果有一个盒子B相对盒子A进行定位,则以盒子A偏移前的位置为准。
absolute 盒子根据’top’,’right’,’bottom’,’left’来进行定位,偏移量是相对于这个盒子的包含块而言的。显然Absolute定位是脱离普通流的,这意味这它不会影响后面元素的布局。虽然absolute positioning有margins属性,但不会因为别的元素的margins而崩塌(原因在后文的BFC中会提到)。
fixed 盒子参照某些固定的参考系,然后根据上述absolute的方式进行定位。同样的,它的margins属性不会因为别的元素的margins而崩塌。当媒体类型为「handheld,projection,screen,tty和tv」的情况下,该盒相对于viewport固定,并且滚动时不会移动。当媒体类型为「print」时,这个盒子相对page box固定,并且在每个页面都会渲染。可以用@media媒体查询来进行区分。

从上述表格来看,position为static/relative的盒子遵循Normal flow,而position为absulute/fixed的盒子则属于Absulute position。

5. BFC是什么

前端从业人员对于BFC这个词估计都不陌生了,但是关于它的作用还是需要花一些心思来理解清楚的。

BFC(block formatting context)直译为”块级格式化上下文”。前文提到,只有块元素会参与到BFC的渲染。它规定了在BFC中,块级盒的布局规则:

  • 在一个块格式化上下文中,盒在垂直方向一个接一个地放置,从包含块的顶部开始。
  • 两个兄弟盒之间的垂直距离由’margin’属性决定,属于同一个BFC的两个相邻盒的margin会发生重叠 ,由此可以看出,上文提到的margin collapse现象正是由于两个块是在同一个BFC导致的
  • 在一个块格式化上下文中,每个盒的左外边界挨着包含块的左外边界,即使存在浮动也是如此。
  • BFC的区域不会与float box重叠,但是浮动元素参与计算BFC的高度。

生成BFC的条件

  • 根元素
  • float属性不为none
  • position为absolute或fixed
  • display为inline-block, table-cell, table-caption, flex, inline-flex,grid,inline-grid等
  • overflow不为visible

BFC可以解决的一些问题

现象1:两个block box的margin collapse问题

解决办法:给其中一个或两个block box通过上述方式(通常是设置overflow)生成新的BFC

现象2:让父容器不会被内部的float元素撑高(清除内部浮动)

解决办法:前文提到,float元素也是参与BFC的高度计算的,因此也可以通过触发父元素的BFC来达到这个效果

现象3:元素会被浮动元素覆盖

解决办法:可以给元素触发BFC,这样元素会通过压缩宽度来达到和float元素并列布局

一个新的 display 属性的值,它可以创建无副作用的BFC。在父级块中使用 display: flow-root 可以创建新的BFC

总结

通过深入理解BFC,IFC,包含块的概念以及position和dispaly属性的作用,对我们巩固CSS2.1基础非常重要,同时一些看似玄学的问题也迎刃而解了。适当的阅读规范确实对知识点的理解帮助较大。