CSS魔法堂:從新認識Box Model、IFC、BFC和Collapsing margins

前言                                
  盒子模型做爲CSS基礎中的基礎,曾一度覺得掌握了IE和W3C標準下的塊級盒子模型便可,但近日在學習行級盒子模型時發現原來當初是如此幼稚好笑。本文嘗試全面敘述塊級、行級盒子模型的特性。做爲近日學習的記錄。

何爲盒子模型?                             
  盒子模型到底何方神聖竟然能夠做爲CSS的基礎?聞名不如見面,上圖了喂!

  再來張切面圖吧!

  下面咱們以 <div></div> 爲栗子。 <div></div> 標籤被瀏覽器解析後會生成div元素並添加到document tree中,但CSS做用的對象並非document tree,而是根據document tree生成的render tree,而盒子模型就是render tree的節點。
  * 注意:
  * 1. CSS做用的是盒子(Box), 而不是元素(Element);
  * 2. JS沒法直接操做盒子。

  盒子模型的結構
  因爲塊級盒子在驗證效果時干擾信息更少,便於理解盒子模型,所以下面將以塊級盒子模型來說解。
  注意: 行級盒子模型與塊級盒子模型結構一致,只是行級盒子在此基礎上有自身特性而已。
  從上面兩幅圖說明盒子模型其實就是由如下4個盒子組成:
  1. content box:必備,由content area和4條content/inner edge組成;
  2. padding box:可選,由padding和4條padding edge組成。若padding寬度設置爲0,則padding edge與content edage重疊;
  3. border box:可選,由border和4條border edge組成。若border寬度設置爲0,則border edge與padding edage重疊;
  4. margin box:可選,由margin和4條margin/outer edge組成。若margin寬度設置爲0,則margin edge與border edage重疊。
  對於剛接觸CSS的同窗,常常會將"經過width/height屬性設置div元素的寬/高"掛在口邊,其實這句話是有誤的。
  1. 首先css屬性width和height做用於div元素所產生的盒子,而不是元素自己;
  2. 另外盒子模型由4個盒子組成,那width和height究竟是做用於哪些盒子呢?
  這裏就分爲IE盒子模型和標準盒子模型了。
   IE box model    
IE5.5(怪異模式)採用IE盒子模型,其它將使用W3C標準盒子模型。
css

width = content-width + padding-width + border-width
height = content-height + padding-height + border-height

 
  Standard box model  
html

width = content-width
height = content-height


遊走於IE box model 和 Standard box model間的通道——box-sizing屬性
  咱們看到存在兩種width/height的劃分方式,到底哪一種纔對呢?其實兩種都對,具體看如何使用而已。另外IE8開始支持CSS3屬性box-sizing,讓咱們能夠自由選擇採用哪一種盒子:)
  box-sizing:content-box/border-box/inherit
                  content-box——默認值,採用Standard box model
                  border-box——採用IE box model
                  inherit——繼承父元素屬性值
sample:web

Element{
  -moz-box-sizing: border-box; // FireFox3.5+
  -o-box-sizing: border-box; // Opera9.6(Presto內核)
  -webkit-box-sizing: border-box; // Safari3.2+
  -ms-box-sizing: border-box; // IE8
  box-sizing: border-box; // IE9+,Chrome10.0+,Safari5.1+,Opera10.6
}


行級盒子——懷疑人生de起點:)                  
  以前我理解的盒子模型如上所述,當我看到行級盒子的種種現象時,便開始懷疑人生了:(
 width/height不起做用。。。segmentfault

.defined-wh{
  width: 100px;
  height: 50px;

  border: solid 1px red;
  background: yellow;
}

對於block-level box瀏覽器

<div class="defined-wh"></div>


對於inline-level boxapp

<span class="defined-wh"></span>


  行級盒子的寬度怎麼會是0呢?高度是有的但不是50px啊,到底什麼回事啊?
  緣由很簡單,那就是行級盒子的content box的高/寬根本就不是經過height/width來設置的。
  content box/area的高由font-size決定的;
  content box/area的寬等於其子行級盒子的外寬度(margin+border+padding+content width)之和。

  行級盒子被擠斷了。。。ide

.broken{
  border: solid 1px red;
  background: yellow;
}

對於block-level box佈局

<div class="broken">一段文字一段文字一段文字一段文字一段文字一段文字</div>


對於inline-level box學習

<span class="broken">一段文字一段文字一段文字一段文字一段文字一段文字</span>


行級盒子被五馬分屍了,可憐兮兮的。更可憐的是我理解不了。。。
其實W3C Recommendation有說明的哦!
>The box model for inline elements in bidirectional context
>When the element's 'direction' property is 'ltr', the left-most generated box of the first line box in which the element appears has the left margin, left border and left padding, and the right-most generated box of the last line box in which the element appears has the right padding, right border and right margin.
>When the element's 'direction' property is 'rtl', the right-most generated box of the first line box in which the element appears has the right padding, right border and right margin, and the left-most generated box of the last line box in which the element appears has the left margin, left border and left padding.
  就是說當inline-level box寬度大於父容器寬度時會被拆分紅多個inline-level box,
當屬性direction爲ltr時,margin/border/padding-left將做用於第一個的inline-level box,margin/border/padding-right將做用於最後一個的inline-level box;若屬性direction爲rtl時,margin/border/padding-right將做用於第一個的inline-level box,margin/border/padding-left將做用於最後一個的inline-level box。
看到了沒?行級盒子真的會被分屍的,好殘忍哦:|

 行級盒子怎麼不佔空間了?怎麼刷存在感啊。。。flex

.existed{
  margin: 20px;
  padding: 20px;
  border: solid 1px red;
  background: yellow;
  background-clip: content-box;
}

對於block-level box

<div>before bababababababa</div>
<div class="existed">babababababababababa</div>
<div>after bababababababa</div>


對於inline-level box

<div>before bababababababa</div>
<span class="existed">babababababababababa</span>
<div>after bababababababa</div>


  看,行級盒子的margin/border/padding-top/bottom怎麼均不佔空間的?難道行級盒子僅有content box佔空間嗎?
這裏已經涉及到水平和垂直方向排版的範疇了,僅以盒子模型已沒法解析理解上述的問題。
(要結合https://www.w3.org/TR/CSS2/box.html和https://www.w3.org/TR/CSS21/visuren.html、https://www.w3.org/TR/CSS21/visudet.html來理解了!)

在深刻解釋inline-level box的上述現象前,咱們須要補充一下:
1. 一個元素會對應0~N個box;(當設置`display:none;`時,則對應0個box)
2. 根據`display`屬性值,元素會對應不一樣類型的controlling box(inline/block-level box均是controlling box的子類). 就CSS2而言`display:inline|inline-block|inline-table|table-cell|table-column-group`的元素對應inline-level box,而`display:block|list-item|table|table-caption|table-header-group|table-row|table-row-group|table-footer-group`的元素則對應block-level box;
3. box佈局/排版時涉及到定位問題,而CSS中經過positioning scheme來定義,其包含normal flow、floats和absolute positioning三種定位方式.而normal flow包含block formatting、inline formatting和relative positioning,其中BFC爲block formatting的上下文,IFC爲inline formatting的上下文。

所以你們請注意,前方高能,前方高能!!!

 和IFC一塊兒看inline-level box
  IFC(Inline Formatting Context),直譯爲「行內格式化上下文」,這是什麼鬼的翻譯啊?反正我對於名詞一貫採用拿來主義,理解名詞背後的含義纔是硬道理。
  咱們簡單理解爲每一個盒子都有一個FC特性,不一樣的FC值表明一組盒子不一樣的排列方式。有的FC值表示盒子從上到下垂直排列,有的FC值表示盒子從左到右水平排列等等。而IFC則是表示盒子從左到右的水平排列方式,僅此而已(注意:一個盒子僅且僅有一個FC值)。而inline-level box的FC特性值固定爲IFC
  另外僅處於in-flow的盒子才具備FC特性,也就是positioning scheme必須爲Normal flow的盒子才具備FC特性。
  除了IFC外,對於inline-level box排版而言還有另外一個重要的對象,那就是line box。line box是一個看不見摸不着的邊框,但每一行所佔的垂直高度實際上是指line box的高度,而不是inline-level box的高度。
  line box的特色:
  1. 同一行inline-level box均屬於同一個line box;
  2. line box高度的計算方式(https://www.w3.org/TR/CSS21/visudet.html#line-height)
>The height of each inline-level box in the line box is calculated. For replaced elements, inline-block elements, and inline-table elements, this is the height of their margin box; for inline boxes, this is their 'line-height'.
>The inline-level boxes are aligned vertically according to their 'vertical-align' property. In case they are aligned 'top' or 'bottom', they must be aligned so as to minimize the line box height. If such boxes are tall enough, there are multiple solutions and CSS 2.1 does not define the position of the line box's baseline.
>The line box height is the distance between the uppermost box top and the lowermost box bottom.

.parent{
  line-height: 1;
  font-size: 14px;
 
  border: solid 1px yellow;
}
.child{
  font-size: 30px;
  vertical-align: middle;
 
  border: solid 1px blue;
}
.inline-block{
  display: inline-block;
  overflow: hidden;
 
  border: solid 1px red;
}
.other{
  border: solid 1px green;
}
<span class="parent">
  <span class="child">
    <span class="inline-block">display:inline-block元素</span>
    xp子元素的文字
  </span>
  xp父元素的文字
</span>
<div class="other">其餘元素</div>


1. 根據規則,span.parent所在行的line box的高度受span.parent、span.child、span.inline-block元素對應的inline-level box"高度"的影響。其中span.parent的"高度"爲其line-height實際值,span.child的"高度"爲其line-height實際值,而span.inline-block的"高度"爲其margin box的高度。因爲設置line-height:1,所以span.parent和span.child的content box高度等於line-height實際值;
2. 根據vertical-align屬性垂直對齊,形成各「高度」間並不以上邊界或下邊界對齊;
3. span.inline-block紅色的上邊框(border top)到span.child藍色的下邊框(border bottom)的距離再減去1px即爲line box的高度。(line box的下界實際上是span.child的content box的下限的,你看"其餘元素"的上邊框不是和span.child的下邊框重疊了嗎?若是那是line box的下界,那怎會出現重疊呢)

這裏又涉及到另外一個屬性vertical-align了,因爲它十分複雜,仍是另開文章來敘述吧!

                      行級盒子小結                          
**就盒子模型而言**
  1. inline-level box與block-level box結構一致;
  2. content box的高度僅能經過屬性font-size來設置,content box的寬度則自適應其內容而沒法經過屬性width設置;
  3. 當inline-level box的寬度大於containing block,且達到內容換行條件時,會將inline-level拆散爲多個inline-level box並分佈到多行中,而後當屬性direction爲ltr時,margin/border/padding-left將做用於第一個的inline-level box,margin/border/padding-right將做用於最後一個的inline-level box;若屬性direction爲rtl時,margin/border/padding-right將做用於第一個的inline-level box,margin/border/padding-left將做用於最後一個的inline-level box。
 
**垂直排版特性**
  inline-level box排版單位不是其自己,而是line box。重點在於line box高度的計算。
  1. 位於該行上的全部in-flow的inline-level box均參與該行line box高度的計算;(注意:是全部inline-level box,而不只僅是子元素所生成的inline-level box)
  2. replaced elements, inline-block elements, and inline-table elements將以其對應的opaque inline-level box的margin box高度參與line box高度的計算。而其餘inline-level box則以line-height的實際值參與line box高度的計算;
  3. 各inline-level box根據vertical-align屬性值相對各自的父容器做垂直方向對齊;
  4. 最上方的box的上邊界到最下方的下邊界則是line box的高度。(表述不夠清晰,請參考實例理解)

Collapsing margins                      
  你們一定聽過或遇過collapsing margins吧,它是in-flow的block-level box排版時的一類現象。說到排版那必須引入另外一個FC特性值——BFC(Block Formatting Context)的。
  BFC則是表示盒子從上到下的垂直排列方式,僅此而已(注意:一個盒子僅且僅有一個FC值)。而block-level box的FC特性值固定爲BFC。
  collapsing margins規則
  1. 元素自身margin-top/bottom collapsing

anonymous block-level box
<div class="margins"></div>
anonymous block-level box
<div class="margins border"></div>
anonymous block-level box
.margins{margin: 50px 0 70px;}
.border{border: solid 1px red;}


當block-level box高度爲0,垂直方向的border和padding爲0,而且沒有in-flow的子元素。那麼它垂直方向的margin將會發生重疊。

  2. 父子元素margin-top/top 或 margin-bottom/bottom collapsing

anonymous block-level box
<div class="parent-margins">
  <div class="margins border"></div>
  anonymous block-level box
  <div class="margins border"></div>
</div>
anonymous block-level box
<div class="parent-margins border">
  <div class="margins border"></div>
  anonymous block-level box
  <div class="margins border"></div>
</div>
anonymous block-level box
.parent-margins{margin: 25px 0;}
.margins{margin: 50px 0 25px;}
.border{border: solid 1px red;}


當父子元素margin-top間或margin-bottom間沒有padding、border阻隔時,則會margin會發生重疊。
注意空白字符會形成目標父子元素間的存在anonymous block-level box,致使margin不重疊。

anonymous block-level box
<div class="parent-margins">&nbsp;
  <div class="margins border"></div>
  anonymous block-level box
  <div class="margins border"></div>
</div>
anonymous block-level box



  3. 兄弟元素margin-bottom/top collapsing

<div class="margins">former</div>
<div class="margins">latter</div>
.margins{margin: 50px 0 25px;}

兩個相鄰的in-flow block-level box的上下margin將發生重疊。

**上述爲默認狀況下block-level box(即display:block,其它爲默認值時)的margin重疊規則**
  那非默認狀況下呢?相比非默認狀況下的margin重疊規則,咱們更關心是何時不會產生重疊。這時又引入了另外一個概念——生成新BFC。也就是block-level box A與block-level box B的FC特性值BFC多是不一樣的。
  當兩個相鄰box的FC值不爲同一個BFC時,它們的margin絕對不會重疊。
  那麼剩下的問題就是,到底什麼時候會產生新的BFC?哪些block-level box會採用新的BFC?默認BFC又是誰生成的呢?
  其實根元素(html)會生成默認BFC供其子孫block-level box使用。
  採用floats或absolute positioning做爲positioning scheme時,或display:inline-block/table-cell/table-caption/flex/inline-flex或overflow屬性值不爲visible時,則會產生新的BFC;而新的BFC將做爲子孫block-level box的FC屬性值。
  注意:
    1. 產生新BFC的盒子不會與子盒子發生margin重疊;
    2. display:inline-block的盒子不與 兄弟 盒子發生margin重疊,是由於display:inline-block的盒子的FC特性值爲IFC,還記得line box嗎?沒有margin重疊是天然不過的事了;
    3. positioning scheme爲floats的盒子不與floated的兄弟盒子發生margin重疊,也不會與前一個in-flow的兄弟盒子發生margin重疊。(注意:與父盒子也不會發生margin重疊)

<div class="margins border">sibling</div>
<div class="margins border float">floats1</div>
<div class="margins border float">floats2</div>
.margins{margin: 50px 0 50px;}
.border{border: solid 1px red;}
.float{float:left;width:200px;}

概括FC、BFC和IFC                      

因爲上述主要闡述inline/block-level box,所以經過「僅此而已」來簡化BFC和IFC的內涵。下面咱們稍微全面一點去理解BFC和IFC如何影響inline/block-level box。

FC(Formatting Context),用於初始化時設置盒子自身尺寸和排版規則。注意「初始化」,暗指positioning scheme採用的是normal flow,要知道floats和absolute positioning均不是默認/初始化值。也就是說咱們在討論FC及BFC和IFC時,均針對in-flow box而言的。
  BFC
**對於不產生新BFC的盒子**
1. block-level boxes垂直排列,盒子的left outer edge與所在的containing block的左邊相接觸,默認狀況下(width爲auto時)right outer edge則與所在的containing block的右邊相接觸。即便存在floated的兄弟盒子。

<div id="container" style="border:solid 2px red;">
  <div id="left" style="float:left;width:300px;height:30px;background:yellow;opacity:0.2;"></div>
  <div id="right" style="height:30px;background:#999;"></div>
</div>

雖然 div#left 浮點了,但 div#right 的left outer edge仍是與 div#container 的left content edge相接觸。 div#right 所在的containing block就是 div#container 的content box.
2. block-level box高度的計算
The element's height is the distance from its top content edge to the first applicable of the following:
    the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
    the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin does not collapse with the element's bottom margin
    the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
    zero, otherwise
Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored, and relatively positioned boxes are considered without their offset).

也就out-flow box不影響block-level box高度的計算。也就是解釋了爲什麼div中僅含floated元素時,div盒子高度爲0的現象了。

**對於產生新BFC的盒子**
  對於產生新BFC的盒子而言,除了不發生collapsing margins的狀況外,還有兩個與浮點相關的現象。
1. out-flow box歸入block-level box高度的計算
In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge, then the height is increased to include those edges. Only floats that participate in this block formatting context are taken into account, e.g., floats inside absolutely positioned descendants or other floats are not.
也就positioning scheme爲floats的box也會影響block-level box高度的計算。

2. 誓死不與positioning scheme爲floats的兄弟盒子重疊
The border box of a table, a block-level replaced element, or an element in the normal flow that establishes a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the margin box of any floats in the same block formatting context as the element itself. If necessary, implementations should clear the said element by placing it below any preceding floats, but may place it adjacent to such floats if there is sufficient space. They may even make the border box of said element narrower than defined by section 10.3.3. CSS2 does not define when a UA may put said element next to the float or by how much said element may become narrower.

產生新BFC的block-level box不與floated-box重疊,而是floated-box的margin-box與block-level box的border-box相接觸。
水平方向

<div style="float:left;width:100px;border: solid 1px red;margin-right:50px;">floated</div>
<div style="width:200px;border: solid 1px blue;margin-left:100px;overflow:hidden;">gen new BFC balabala</div>


垂直方向

<div style="float:left;width:100px;border: solid 1px red;margin-bottom:50px;">floated</div>
<div style="width:200px;border: solid 1px blue;margin-top:100px;overflow:hidden;">gen new BFC balabala</div>

 

 IFC

提起IFC那就不能不說line box,而line box高度的計算方式上面已經敘述了,那line box的寬度呢?
line box默認狀況下左邊框與containing block的左邊框接觸,右邊框與containing block的右邊框接觸。若存在floated兄弟盒子,則line box的寬度爲containing block的寬度減去floated-box的outer-box的寬度。

而inline-level box必須包含在line box中,若inline-level box的`white-space:nowrap或pre外的其餘值`時,就會將inline-level box拆分爲多個inline-level box並散落到多個line box中,從而實現文字環繞圖片的效果了。

不然inline-level box會捅破line box(即line box寬度不變)

    行——換與不    

先看看關於換行的CSS屬性吧!

white-space
    normal: 忽略/合併空白
    pre: 保留空白,如同<pre>的行爲
    nowrap: 忽略/合併空白,文本不會換行,直到遇到<br/>
    pre-wrap: 保留空白,可是會正常地進行換行
     pre-line: 忽略/合併空白,可是會正常地進行換行
    inherit: 從父元素繼承。
  word-wrap
    normal: 只在容許的斷字點換行
    break-word: 在長單詞或URL地址內部進行換行
  word-break
    normal:依照亞洲和非亞洲語言的文本規則,容許在單詞內換行。
    keep-all:讓亞洲語言文本如同非亞洲語言文本那樣不容許在任意單詞內換行。
    break-all:容許非亞洲語言文本行如同亞洲語言文本那樣能夠在任意單詞內換行。

具體示例可參考:css中強制換行word-break、word-wrap、white-space區別實例說明

在處理換行問題上,咱們要處理的對象分爲亞洲語言文本和非亞洲語言文本。對於亞洲語言文本是以字做爲操做單元,而非亞洲語言文本是以單詞做爲操做單元。而換行是針對特定語言文本的操做單元來處理,因此默認狀況下會看到一串沒空格的「中文」自動換行,而一串沒空格的「英文」卻沒有換行的現象。
對於咱們(亞洲人)而言,通常採用 word-break:break-all;word-wrap:break-word; 來實現中英文自動換行效果,但英文單詞自己是不能這樣簡單粗暴地換行的。
英語單詞移行有必定規則,概括以下:
1.移行處要用連字符號「-」,只佔一個印刷符號的位置並放在該行的最後.
2.移行時通常按照音節進行,故只可在兩音節之間分開,不能把一個完整的音節分寫在上下兩行.例如:Octo-ber(正),Octob-er(誤).
3.複合詞要在構成該詞的兩部分之間移行.如:some-thing,bed-room等.
4.若是複合詞原來就有連字符號,則就在原連字符號處分行.如:good-looking等.
5.兩個不一樣的輔音字母在一塊兒時,移行時先後各一個.如:cap-tain,ex-pose等.
6.當兩個音節間只有一個輔音字母時,若是該輔音字母前的元音字母按重讀開音節的規則發音,該輔音字母移至下一行.如:fa-ther等.但若是元音按重讀閉音節的規則發音,則該輔音字母保留在上一行末尾.例如:man-age等.
7.當遇到雙寫輔音字母時,通常把它們分紅先後各一個.例如:mat-ter等.
8.當重讀音節在後面時,元音字母前的輔音字母一般移到下一行.如:po-lite等.
9.單音節詞不可移行.如:length,long,dance等.
10.前綴或後綴要保持完整,不可分開寫.如:unfit,disappear等.
11.阿拉伯數字不分開移行書寫.
12.不論音節多少,專有名詞不宜分寫.例如:Nancy,Russia等.
13.縮寫詞、略寫詞或某些詞的縮寫形式不可移行書寫.例如:U.N.(聯合國),P.R.C.(中華人民共和國),isn't.
14.不能構成一個音節的詞尾不分寫.例如:stopped等.
15.字母組合或輔音連綴不可移行.例如:machine,meat等.

CSS簡化了上述的規則,若須要換行處剛好是一個複合詞,就在原連字符號處分行;其它狀況則整個單詞移到下一行。所以使用 word-wrap:break-word; 就OK了。

另外咱們還能夠經過 word-break:keep-all;white-space:nowrap; 來實現打死都不換行的效果


總結                              
  洋洋灑灑總算把Box Model、BFC和IFC描述了個大概。對於BFC折騰點就是在collapsing margins那,另外還有產生新BFC這個行爲上(這個跟浮動等有交集,之後再理清吧);而IFC重點在於理解line box,其實line box也像block-level box那樣是垂直排列的,而inline-level box則是以line box做爲容器實現水平排列罷了。到這裏會發現理解IFC比BFC蛋疼多了,不過有了這篇做基礎,後面理解text-align、line-height和vertical-align就輕鬆很多了。

  本文純我的理解,如有紕漏,望各位指正,謝謝!
  尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/5259121.html肥子John^_^

 

感謝                              

http://div.io/topic/834?page=1#3261(BFC)

http://www.cnblogs.com/giggle/p/5236982.html(BFC)

https://segmentfault.com/a/1190000003043991 (IFC)

http://www.cnblogs.com/winter-cn/archive/2013/05/11/3072929.html(BFC/IFC)

[KB010: 常規流( Normal flow ) ](http://www.w3help.org/zh-cn/kb/010/)[CSS 101: Block Formatting Contexts](http://yuiblog.com/blog/2010/05/19/css-101-block-formatting-contexts/)

相關文章
相關標籤/搜索