一些基本概念
- viewport: 展现网页的媒体,比如窗口或者某个区域,它的大小是有限制的,为了不被平台术语所束缚,我们给他起名viewport,中文意思就是视口。
- canvas: 而我们在渲染网页的时候通常并不知道我们需要多大的空间,而且这些空间通常尺寸会超过viewport的大小,于是实际上我们需要设想一个无限大的画布来绘制我们的元素,我们把它称为canvas。
- box: element(元素)和node(节点)是大家很熟悉的概念,当我们做布局计算的时候,通常会把节点变成box,一个节点可能产生多个box,伪元素也会产生box。
- render tree: 对应于dom树,我们把box的包含关系构成的属性结构成为渲染树render tree
BFC
首先我们来考虑一个基本的情况:
<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是可以嵌套滴:
<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:
<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,那些行内元素,会自动套一个匿名的块级行盒。
<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鸟:
<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里面可能有文字、行内元素混合排布:
<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中的几条重要的基准线:
在水平方向,文字的排布主要受到以下属性影响
- 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很好理解,而行内元素则比较复杂,特别是当行内元素自身又是容器的时候:
<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>
<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>
浮动
浮动是个行级的行为,当遇到浮动元素的时候,会首先"假装"它是个行内元素进行排版,排好后就往浮动的方向挤到挤不过去为止(遇到边界或者其它浮动元素)。
<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>
<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的时候,浮动元素总是挤到边界,在垂直方向上的行为类似"换行"。
<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。重排的时候,行盒会躲开浮动元素。之后的块级盒(不论是行盒还是其它盒)也都会躲开浮动元素排布。
最后赠送文中使用的查看盒边框的小工具,欢迎自己来写网页看盒子: