包含块

什么是包含块

如果有两个 div,其中一个是父元素,另外一个是子元素,则父元素会决定子元素的大小和定位。包含块是什么呢?简单来说,就是可以决定一个元素的大小和定位的元素。

包含块是视觉格式化模型的一个重要概念,它跟 CSS 盒子模型类似。你也可以将包含块理解为一个矩形盒子,这个矩形的作用是为这个矩形内部的后代元素(子元素、孙元素等)提供一个参考。一个元素的大小和定位往往是由该元素所在的包含块决定的。

通常情况下,一个元素的包含块是由离它最近的“块级祖先元素”的“内容边界”决定的。但当元素被设置为绝对定位时,该元素的包含块是由离它最近的 position:relative 或 position:absolute 的祖先元素决定的。一个元素生成的盒子会扮演该元素的内部元素包含块的角色。也就是说,一个元素的 CSS 盒子为它的内部元素创建了包含块。

包含块判定以及包含块范围

一个元素会为它的内部元素创建包含块,内部元素的大小以及定位都跟它的包含块有关。但是并不能说一个元素的包含块就是它的父元素。

根元素

根元素(html 元素),是一个页面中最顶端的元素,它没有父元素。根元素存在的包含块,被称为初始包含块(initial containing block)。

固定定位元素

如果元素的 position 属性为 fixed 那么它的包含块就是当前的可视窗口,也就是当前浏览器的窗口。该元素不会随着浏览器拖动而改变位置。

静态定位元素和相对定位元素

如果元素的 position 属性为 static 或 relative,那么它的包含块是由离它最近的块级祖先元素创建的。祖先元素必须是 block、inline-block 或者 table-cell 类型。

绝对定位元素

如果元素的 position 属性为 absolute,那么它的包含块是最近的 position 属性不为 static 的祖先元素。这里的祖先元素可以是块元素,也可以是行内元素。

如果找不到 position 属性不为 static 的祖先元素,则它的包含块是 body 元素,这也是为什么默认情况下,绝对元素是相对浏览器窗口来定位的。

对于绝对定位元素包含块的范围,我们也分以下两种情况考虑。如果祖先元素是块元素,则包含块的范围为祖先元素的 padding edge。如果祖先元素是行内元素,则包含块取决于祖先元素的 direction 属性。

层叠上下文

什么是层叠上下文

层叠上下文,即 stacking context,是 HTML 中的一个三维的概念。从“z-index 属性”这 一节我们知道,虽然一个网页是平面的,但实际上网页是三维结构,除了 x 轴、y 轴,它还有 z 轴。 其中,z 轴用来设定层的先后顺序。

层叠上下文(stacking context)跟块级格式上下文(BFC)相似,是可以创建出来的。也就是说,跟创建 BFC 一样,你可以在 CSS 中添加一定的属性用于为某个元素创建一个层叠上下文。 如果一个元素具备以下任何一个条件(不考虑 CSS3),则该元素会创建一个新的层叠上下文:根元素。z-index 不为 auto 的定位元素。

什么是层叠级别

层叠级别,即 stacking level。从上面我们知道,可以使用 z-index 属性为一个元素创建一个新的层叠上下文。但一个元素往往会有背景色、浮动子元素、定位子元素等,那么这些东西又是遵 循着怎样的顺序来堆叠的呢?

同一个层叠上下文的背景色以及内部元素,谁在上谁在下,这些都是由层叠级别(stacking level)来决定的。也就是说,层叠级别是针对同一个层叠上下文而言的。层叠级别与层叠上下文是两个不同的概念。

在同一个层叠上下文中,层叠级别从低到高排列如下:

① 边框和背景:也就是当前层叠上下文的边框和背景。

② 负 z-index :z-index 为负值的“内部元素”。

③ 块盒子:普通文档流下的块盒子(block-level box)。

④ 浮动盒子:非定位的浮动元素(也就是排除了 position:relative 的浮动盒子)。

⑤ 行内盒子:普通文档流下的行内盒子(inline-level box)。

⑥ z-index:0:z-index 为 0 的“内部元素”。

⑦ 正 z-index :z-index 为正值的“内部元素” image.png

层叠上下文的特点

一个元素在 z 轴方向上的堆叠顺序,是由“层叠上下文”和“层叠级别”这两个因素决定的。

▶ 在同一个层叠上下文中,我们比较的是“内部元素层叠级别”。层叠级别大的元素显示在上,层叠级别小的元素显示在下。

▶ 在同一个层叠上下文中,如果两个元素的层叠级别相同(即 z-index 值相同),则后面的元素堆叠在前面元素的上面,遵循“后来者居上”原则。

▶ 在不同的层叠上下文中,我们比较的是“父级元素层叠级别”。元素显示顺序以“父级层叠上下文”的层叠级别来决定显示的先后顺序,与自身的层叠级别无关。

同一层叠上下文中,元素设置为浮动之后,它的层叠级别比正常文档流下的块级盒子的层叠级别要高,此时浮动元素会浮到上面去,脱离文档流。

BFC 和 IFC

基本概念

在 CSS 中,页面的任何一个元素都可以看成是一个盒子。在普通文档流(normal flow)中, 盒子会参与一种格式上下文(formatting context)。这个盒子可能是块盒子(block-level box), 也可能是行内盒子(inline-level box)。一个盒子只能是块盒子或者是行内盒子,不能是块盒子的同时又是行内盒子。其中块盒子参与 BFC(块级格式上下文),行内盒子参与 IFC(行级格式 上下文)。

格式上下文

格式上下文是 W3C CSS2.1 规范中的一个重要概念,它指的是页面中的一块渲染区域。除此之外,这个格式上下文有一套自己的渲染规则。格式上下文决定了其内部元素将如何定位,以及和其他元素之间的关系。

格式上下文有以下两种类型。 块级格式上下文,即 Block Formatting Context,简称 BFC。行级格式上下文,即 Inline Formatting Context,简称 IFC。

盒子

盒子,又称 CSS 盒子,是 CSS 布局的基本单位。简单来说,一个页面就是由很多个盒子组成的。元素的类型和 display 属性决定了盒子的类型。不同类型的盒子,会参与不同的格式上下文。 image.png

BFC

创建 BFC

浮动元素,绝对定位元素(position 为 absolute 或 fixed),元素类型(即 display 属性)为 inline-block、table-caption、table-cell,以及 overflow 属性不为 visible 的元素将会创建一个新的块级格式上下文(BFC)。

如果一个元素具备以下任何一个条件,则该元素都会创建一个新的 BFC:

▶ 根元素

▶ float 属性除了 none 以外的值,也就是 float:left 和 float:right。

▶ position 属性除了 static 和 relative 以外的值,也就是 position:absolute 和 position:fixed。

▶ overflow 属性除了 visible 以外的值,也就是 overflow:auto、overflow:hidden 和 overflow:scroll。

▶ 元素类型(即 display 属性)为 inline-block、table-caption、table-cell。

也就是说,如果我们要创建一个新的 BFC,只需要在 CSS 中添加上面任意一个属性就可以了。 创建新的 BFC 可以帮助我们解决很多问题。这里要注意一下,根元素也会 创建 BFC。也就是说,默认情况下一个页面中所有的元素都处于同一个 BFC 中,不需要我们去设置。虽然这些属性都可以创建 BFC,但是也会 产生如下的一些影响:

▶ float:left 和 float:right 会将元素移到左边或右边,并被其他元素环绕。

▶ overflow:hidden 会将超出元素的内容隐藏。

▶ overflow:scroll 会产生多余的滚动条。

▶ display:table 可能引发响应性问题。

注意:根据定义,类型为 block、table 的元素不会创建 BFC。元素类型(即 display 属 性)为 block、table、list-item 的元素,会生成块盒子(block-level box),然后块盒子会参与 BFC,即 block、table、list-item 等类型的元素是参与 BFC,而不是创建 BFC。

BFC 特点

W3C 标准描述 BFC 的特点共有两条:在一个 BFC 中,盒子从顶端开始一个接着一个地垂直排列,两个相邻盒子之间的垂直间距由 margin 属性决定。在同一个 BFC 中,两个相邻块盒子之间垂直方向上的外边距会叠加。在一个 BFC 中,每一个盒子的左外边界(margin-left)会紧贴着包含盒子的容器的左边(border-left)(对于从右到左的格式化,则相反),即使存在浮动元素也是如此。

这里进一步分析得到以下几点:

▶ 在一个 BFC 内部,盒子会在垂直方向上一个接着一个排列。

▶ 在一个 BFC 内部,相邻的 margin-top 和 margin-bottom 会叠加。

▶ 在一个 BFC 内部,每一个元素的左外边界会紧贴着包含盒子的容器的左边,即使存在浮 动也是如此。

▶ 在一个 BFC 内部,如果存在内部元素是一个新的 BFC,并且存在内部元素是浮动元素, 则该 BFC 的区域不会与 float 元素的区域重叠。

▶ BFC 就是页面上的一个隔离的盒子,该盒子内部的子元素不会影响外部的元素。

▶ 计算一个 BFC 的高度时,其内部浮动元素的高度也会参与计算。

为什么在一个 BFC 中,盒子会在垂直方向上一个接着一个排列呢?如 果在一个 BFC 中有一个盒子是 span 这种行内元素,岂不是不符合了?再次提醒一下,能够参与 BFC 中的盒子是块盒子(block-level box)。就算在这个 BFC 中存在一个行内元素,这个行内元素参与的是 IFC,而不是 BFC,别混淆了。

BFC 用途

创建 BFC 避免垂直外边距叠加

外边距叠加,准确地说是指在同 一个 BFC 中,两个相邻的 margin-top 和 margin-bottom 相遇时,这两个外边距将会合并为一个外边距,即“二变一”。其中,叠加之后的外边距高度等于发生叠加之前的两个外边距中的最大值。之所以会发生垂直外边距叠加,是因为这是 BFC 的特点。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title></title>
  6. <style type="text/css">
  7. #wrapper
  8. {
  9. width:200px;
  10. border:1px solid gray;
  11. overflow:hidden; /*创建一个新的BFC*/
  12. }
  13. #a,#b
  14. {
  15. height:60px;
  16. line-height:60px;
  17. text-align:center;
  18. font-size:30px;
  19. color:White;
  20. background-color:Purple;
  21. }
  22. #a{margin-bottom:20px;}
  23. #b{margin-top:30px;}
  24. </style>
  25. </head>
  26. <body>
  27. <div id="wrapper">
  28. <div id="a">A</div>
  29. <div id="b">B</div>
  30. </div>
  31. </body>
  32. </html>

使用 overflow:hidden 为父元素创建一个 BFC,也就是说父元素是一个 BFC 了。因此 A 和 B 位于同一个 BFC 中,这里就发生了垂直外边界叠加,A B 之间的垂直距离为 30px,如果我们没有为父元 素创建 BFC,则默认情况下 A 和 B 就是处于根元素的 BFC 中,此时依然会发生叠加。

解决方式:可以将两个盒子放到不同的 BFC 中:

<!DOCTYPE html> 
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style type="text/css">
      #wrapper
      {
        width:200px;
        border:1px solid gray;
        overflow:hidden; /*创建一个BFC*/
      }
      #bfc-box
      {
        overflow:hidden; /*创建一个BFC,避免外边距叠加*/
      }
      #a,#b
      {
        height:60px;
        line-height:60px;
        text-align:center;
        font-size:30px;
        color:White;
        background-color:Purple;
      }
      #a{margin-bottom:20px;}
      #b{margin-top:30px;}
    </style>
  </head>
  <body>
    <div id="wrapper">
      <div id="a">A</div>
      <div id="bfc-box">
        <div id="b">B</div>
      </div>
    </div>
  </body>
</html>
A 和 B 处于不同的 BFC 中,其中 A 处于 #wrapper 元素的 BFC 中,B 处于 #bfc-box 元素的 BFC 中,所以不会发生垂直外边距叠加。这里要注意:假如我们不给 #bfcbox 元素添加 overflow:hidden,A 和 B 也会发生垂直外边距叠加。但是不对啊,此时 A 和 B 都不属于相邻的元素,为什么它们还会发生外边距叠加呢?  

回看结论:同一个 BFC 内部,相邻的 margin-top 和 margin-bottom 会叠加。这里的“相邻”不是指“相邻的兄弟元素”,而是指相邻的 margin-top 和 margin-bottom。

创建 BFC 来清除浮动

BFC 包含浮动

已知“可以使用 overflow:hidden 来清除浮动”,以及“计算一个 BFC 的高度时,其内部浮动元素的高度也会参与计算”两点,可以得出:如果一个元素是一 个 BFC,则计算该元素高度的时候,内部浮动子元素的高度也得算进去。

<!DOCTYPE html> 
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style type="text/css">
      #wrapper 
      {
        width:200px;
        border: 1px solid black;
        overflow:hidden;
      }
      #first,#second
      {
        width:80px;
        height:40px;
        border:1px solid red;
      }
      #first{float:left;}
      #second{float:right;}
    </style>
  </head>
  <body>
    <div id="wrapper">
      <div id="first"></div>
      <div id="second"></div>
    </div>
  </body>
</html>

把浮动子元素的高度算进去,因此最终父元素的高度等于浮动子元素的高度。此时就相当于清除了浮动。当然我们也可以通过给父元素添加 display:inline-block、float:left 等来创建新的 BFC,以此实现浮动的清除。

BFC 避免文字环绕image.png

举例
<!DOCTYPE html> 
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style type="text/css">
      #wrapper
      {
        width:400px;
        height:200px;
        border:1px solid gray;
        padding:10px;
      }
      img{float:left;}
      #content{background-color:#FFACAC;overflow:hidden;}
    </style>
  </head>
  <body>
    <div id="wrapper">
      <img src="images/ailianshuo.png" alt=""/>
      <div id="content">水陆草木之花,可爱者甚蕃。晋陶渊明独爱菊。自李唐来,世人甚爱牡丹。予独爱
        莲之出淤泥而不染,濯清涟而不妖,中通外直,不蔓不枝,香远益清,亭亭净植,可远观而不可亵玩焉。予谓菊,花之隐逸者也;
        牡丹,花之富贵者也;莲,花之君子者也。噫!菊之爱,陶后鲜有闻;莲之爱,同予者何人? 牡丹之爱,宜乎众矣。</div>
    </div>
  </body>
</html>

这里为 #content 元素添加 overflow:hidden,此时 #content 元素变成了一个新的 BFC,根据结论:“在一个 BFC 内部,如果存在内部元素是一个新的 BFC,并且存在内部元素是浮动元素”,则该 BFC 的区域不会与 float 元素的区域重叠。 image.png

使用 BFC 创建自适应两列布局

自适应两列布局,指的是在左右两列中,有一列的宽度为自适应,另外一列的宽度是固定的。与上面的避免文字环绕方式相似。

<!DOCTYPE html> 
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style type="text/css">
      #sidebar
      {
        float:left;
        width:100px;
        height:150px;
        background:#FF6666;
      }
      #content
      {
        overflow:hidden;
        height:200px;
        background-color:#FFCCCC;
      }
    </style>
  </head>
  <body>
    <div id="sidebar"></div>
    <div id="content"></div>
  </body>
</html>