CSS布局

 

一些基本概念

  • viewport: 展现网页的媒体,比如窗口或者某个区域,它的大小是有限制的,为了不被平台术语所束缚,我们给他起名 viewport,中文意思就是视口。
  • canvas: 而我们在渲染网页的时候通常并不知道我们需要多大的空间,而且这些空间通常尺寸会超过 viewport 的大小,于是实际上我们需要设想一个无限大的画布来绘制我们的元素,我们把它称为 canvas。
  • box: element(元素)和 node(节点) 是大家很熟悉的概念,当我们做布局计算的时候,通常会把节点变成 box,一个节点可能产生多个 box,伪元素也会产生 box。
  • render tree: 对应于 dom 树,我们把 box 的包含关系构成的属性结构成为渲染树 render tree

BFC

首先我们来考虑一个基本的情况:

run

<html>
  <head><title></title></head>
  <body>
    <div style="width:300px;height:50px;"></div>
    <div style="width:300px;height:20px;"></div>
    <div style="height:50px;"></div>
    <div style="width:300px;height:50px;"></div>
  </body>
</html>

之所以简单是因为这些 div 每一个只产生一个盒子,默认的 display 下,div 产生块级盒,为了让块级盒按照我们的意愿排布,我们需要引入一个概念:block formatting context,简称 BFC。

BFC 其实是个相当简单的概念,然而因为实在中文中没啥合适的词来形容(你不觉得 "块级格式化上下文" 比 BFC 更让人觉得不知所云么?),这个概念已经被传得极其神秘:

在 CSS 面试中问 BFC 等概念,就如在 JS 面试中问闭包等概念一样,经常会刷掉一些真正优秀的人。 ——玉伯

甚至很多人连它是动词还是名词都搞不清,不信你可以搜索一下触发 BFC,亲,BFC 是名词啊……怎么它就能触发呢?

简单地说,BFC 就是像例子里面一样顺次竖着排块级盒的一种上下文。竖着排盒子而已,竖着排盒子有什么理解不了的啊我 X?

好吧平复一下情绪,我们继续说,canvas 会设立一个 BFC,这也是最外层的 formatting context 了,问题的复杂性在于有些块级盒内部也可以产生 BFC(至少它必须也能包含块级盒),于是说 BFC 是可以嵌套滴:

run

<html>
  <head><title></title></head>
  <body>
    <div style="width:300px;height:100px;">
      <div style="width:300px;height:20px;"></div>
      <div style="height:50px;"></div>
    </div>
    <div style="height:50px;"></div>
  </body>
</html>

不是所有块级盒内部都可以产生 BFC,比如说要是这盒里面连块级盒都没有,都是行内盒那就产生 IFC:

run

<html>
  <head><title></title></head>
  <body>
    <div style="width:300px;height:100px;overflow:visible;">
      <span></span><span>囧囧</span>
    </div>
    <div style="height:50px;"></div>
  </body>
</html>

不过,只要它的子节点里面有一个块级盒,它就产生 BFC,那些行内元素,会自动套一个匿名的块级行盒。

run

<html>
  <head><title></title></head>
  <body>
    <div style="width:300px;height:100px;overflow:visible;">
      <span></span><div>啦啦啦</div><span>囧囧</span>
    </div>
    <div style="height:50px;"></div>
  </body>
</html>

又比如说 overflow:visible 的块盒就不产生 BFC,不但不产生 BFC,啥 FC 都不产生,它的子元素直接搞进自己外层的 BFC 鸟:

run

<html>
  <head><title></title></head>
  <body>
    <div style="width:300px;overflow:visible;">
      <p style="width:200px;height:20px;"></p>
      <p style="height:50px;">aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa</p>
    </div>
    <p style="height:50px;"></p>
  </body>
</html>

overflow:visible 这个限制只对所谓的块盒(既包含块级盒、自己又是块级盒)存在,有些盒内部也能包含块级元素,但是它本身又不是块级元素(比如 display 为 table-cell、inline-block、或者盒本身是 flex item 等),因为外面不是 BFC,所以它们不论如何一定会给包含的块级盒创建一个新的 BFC 出来。

同一个 BFC 里面的盒还会有 margin collapse 现象,详情见此文 《谈 margin collapsing(外边距叠加)》

IFC

IFC 里面可能有文字、行内元素混合排布:

run

<html>
  <head><title></title></head>
  <body>
    <div style="">
      <span></span>囧囧囧<span>囧囧</span>
    </div>
  </body>
</html>

文本的排布是个很复杂的事情,在 dom 树中,我们存储的是字符和字符串,而在 render tree 中,我们要使用一个新的概念:字型(glyph)。字体文件(通常是 ttf,ture type 格式的)中包含着不同字体下字符到字型的规则。

一个字型有很多属性,下面这张图来自 freetype,它是大部分浏览器使用的文字排版基础库:

详情请看这里

图中的那根加粗的 X 轴就是传说中的基线 (base line) 了,文字默认是按照基线对齐的。

下面这个 demo 展示了一个 IFC 中的几条重要的基准线:

baseline sub super top text-top middle bottom text-bottom
 
great it works!2囧啊

在水平方向,文字的排布主要受到以下属性影响

  • word-break, word-spacing, word-wrap
  • letter-spacing
  • white-space
  • text-align
  • hyphenate-character, hyphenate-limit-after, hyphenate-limit-before, hyphenate-limit-lines, hyphens
  • justify-content
  • ……

总之很复杂,讲也讲不完,而且大家又不要写浏览器所以大家就大概望文生义一下好了(po 主被扔蛋中……)。

对于一个正常的 "行盒" 来说,文字总是垂直居中排布的。

在纵向,文本排布主要受 line-height 属性影响,在有行内盒参与的情况下就复杂了,
一个行内盒的 vertical-align 可以有这样几种取值:

  • baseline
  • sub
  • super
  • top
  • text-top
  • middle
  • bottom
  • text-bottom

它也可以是某一指定高度。根据行内盒的对齐方式,它有可能把这行盒向上或者向下 "撑大", 造成超过行高的情况。

所以行盒在 BFC 中占据的实际高度,很可能大于 line-height。

文字的 baseline 很好理解,而行内元素则比较复杂,特别是当行内元素自身又是容器的时候:

run

<html>
  <head><title></title></head>
  <body>
    <div style="overflow:visible;">
      <span></span>囧囧囧<div style="display:inline-block;width:300px;height:300px;"></div><span>囧囧</span>
    </div>
  </body>
</html>

run


<html>
  <head><title></title></head>
  <body>
    <div style="overflow:visible;">
      <span></span>囧囧囧<div style="display:inline-block;width:300px;height:300px;">123<br/>123<br/>123</div><span>囧囧</span>
    </div>
  </body>
</html>

浮动

浮动是个行级的行为,当遇到浮动元素的时候,会首先 "假装" 它是个行内元素进行排版,排好后就往浮动的方向挤到挤不过去为止(遇到边界或者其它浮动元素)。

run

<html>
  <head><title></title></head>
  <body>
    <div style="">
      <span></span>囧囧囧<div style="display:inline-block;width:300px;height:300px;float:left"></div><span>囧囧</span>
    </div>
  </body>
</html>

run

<html>
  <head><title></title></head>
  <body>
    <div style="">
      <span></span>囧囧囧<div style="display:inline-block;width:300px;height:300px;float:left"></div><span>囧囧</span><br/>
<div style="display:inline-block;width:300px;height:300px;float:left"></div>
    </div>
  </body>
</html>

某一方向有 clear 的时候,浮动元素总是挤到边界,在垂直方向上的行为类似 "换行"。

run

<html>
  <head><title></title></head>
  <body>
    <div style="">
      <span></span>囧囧囧<div style="display:inline-block;width:300px;height:300px;float:left"></div><span>囧囧</span><br/><div style="clear:left;display:inline-block;width:300px;height:300px;float:left"></div>
    </div>
  </body>
</html>

排好一个浮动元素之后,这一行就要重排一次。所以说浮动元素会造成行级的 reflow。重排的时候,行盒会躲开浮动元素。之后的块级盒(不论是行盒还是其它盒)也都会躲开浮动元素排布。

最后赠送文中使用的查看盒边框的小工具,欢迎自己来写网页看盒子:

run

lalala