一些基本概念
- 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。重排的時候,行盒會躲開浮動元素。之后的塊級盒(不論是行盒還是其它盒)也都會躲開浮動元素排布。
最后贈送文中使用的查看盒邊框的小工具,歡迎自己來寫網頁看盒子: