深入CSS基礎之box model

JunYu發表於2019-02-19

閱讀注意事項

  1. 本篇文章的依賴主要是CSS2.1 specification 8. box modelCSS Box Model Module Level 3
  2. 本篇整體比較細節和理論,可能會看起來枯燥,我儘量講的邏輯簡單些。個人認為有時候閱讀枯燥的理論文章是有必要的。
  3. 寫這篇的文章主要目的在於完善自己的知識體系。因此希望大家能夠多多指出文章中不恰當之地方。
  4. 永久博文地址

introduction

box model應該是CSS當中最核心的概念之一了,本篇主要講述CSS當中的box model(盒模型)。

在看本篇內容之前,可以先自己回答下面幾個問題:

  1. 什麼是box model? box model 包含了哪些內容? 它的作用是什麼? 它出現在瀏覽器處理文件的哪一個步驟? 後續步驟是什麼?
  2. collapse margin的細節是什麼?

什麼是box model(盒模型)

CSS2.1 specification 8.box model 第一句話就很好的描述了它的定義:

The CSS box model describes the rectangular boxes that are generated for elements in the document tree and laid out according to the visual formatting model.

從這句話中我們可以得出來box model是什麼: box model是用來描述DOM樹中元素生成的矩形盒子的,並且UAs會根據 visual formatting model 來佈局這些盒子。

box dimension(盒尺寸)

typical box

這一小節是用來描述具體的矩形盒子的,每個盒子會有4塊區域:

  • content area
  • padding area
  • border area
  • margin area 每塊區域的尺寸由各自相關的屬性決定。另外 padding, border, margin 可以被分割成top, bottom, left, right 四個小區域,並由具體的屬性來定義。

下面我們來引入新的名詞(為了後面的內容),edge: 每個盒子的周長被稱為edge,即邊界。相應的,我們可以知道每個box都會有四個edge.

  • content edge or inner edge: content area 的尺寸預設由元素的widthheight 兩個屬性,元素的內容以及它的containing block決定
  • padding edge: padding edge定義了元素的padding box(包含content 和 padding area)
  • border edge: border edge定義了元素的border box(包含content,padding 和 border area)
  • margin edge or outer edge:如果 margin的width為0,那麼margin area和padding area 一樣

Note: 一個box的content area, padding area 和 border area的 background樣式由該元素的 background屬性決定。也就是說background 預設會一直延伸到 border area,而 margin總是透明的。在CSS3中,我們可以使用background新屬性來修改預設情況。

CSS Box model 3 新增加一段: When a box fragments—is broken, as across lines or across pages, into separate box fragments—each of its boxes (content box, padding box, border box, margin box) also fragments. How the content/padding/border/margin areas react to fragmentation is specified in [css-break-3] and controlled by the box-decoration-break property.

這一段主要講的是當一個盒子跨行或者跨頁時會被分割成多個box片段,那麼對於該盒子的content,padding,border,margin在不同的box 片段中該如何表現?這些內容定義在css-break-3中,並且可以使用box-decoration-break來控制。 另外其實在CSS2.1也有描述這樣的場景,在8.6中描述了inline box跨行時盒模型該如何表現。

margin屬性: margin-top, margin-bottom, margin-left, margin-rightmargin

這些屬性都是初學時就已經用的很熟的。因此,這裡只強調幾個注意事項: 先看屬性的定義

'margin-top', 'margin-bottom', 'margin-left', 'margin-right'
Value:  	`<margin-width>` | inherit
Initial:  	0
Applies to:  	all elements except elements with table display types other than table-caption, table and inline-table
Inherited:  	no
Percentages:  	refer to width of containing block
Media:  	visual
Computed value:  	the percentage as specified or the absolute length
複製程式碼
  1. value可以是 <margin-width> ,包含3種具體的取值:
    • <length>: 指定固定的寬度,比如3px, 1em
    • <percentage>: 百分比取值在由computed value轉換成used value的時候計算,基數是generated box's containing blockwidth(也就是該元素的包含塊的width). 而如果containing block的width會依賴該元素,那麼具體表現在CSS 2.1中未定義的。
    • auto, 後面具體講
  2. margin-topmargin-bottom 對於 non-replaced inline elements不起作用。
  3. 在CSS3當中,上面這幾個屬性被稱為physical margin屬性。並且介紹了相對應的logical margin屬性,比如margin-block-start,margin-block-end(這些屬性跟文件的writing mode相關)。(logical margin和physical margin控制的是同樣的margin區域,只是不同的表現形式而已。CSS3加入這個特性是因為不同國家的文件排列形式是不一樣的,比如阿拉伯語就是從右往左寫的)

Note: CSS3中新增的margin-trim屬性由於目前沒有任何瀏覽器支援,這裡不做介紹(當前時間為2019年2月)。

collapsing margins

在CSS當中,2個或多個盒子相鄰的垂直方向margins會合併成一個margin,這種合併被我們稱為collapse(塌陷),而合併後的margin被叫做collapse margin. 首先,我們先強調一些margin不會合並的例外情況:

  1. 水平方向上的margin不會collapse
  2. 對於相鄰的垂直方向上的margin不會塌陷有兩種情況:
    • root元素盒子的 margin不會產生塌陷(在HTML當中就是 html元素)
    • 如果一個帶有clearance(間隙,指的是clear屬性導致元素位置移動產生的間隙)的元素的top margin和 bottom margin 存在相鄰的margin,該元素的相關margin會和緊挨著的兄弟元素的相鄰外邊距合併,但合併後的外邊距不會再和父級塊的下外邊距合併

那麼,如何判斷兩個盒子的margin是相鄰的(adjoining)呢? 需要同時滿足下面那幾個條件:

  • 兩個盒子都屬於in-flow(流內) block-level boxes,並且處於同一個block formatting model
  • 沒有 line box, clearance, padding, border將它們間隔開(這裡高度為0的line box 會被忽略)。
  • 都屬於垂直相鄰的盒邊界,也就是滿足下面的其中一種情況:
    • 盒子的上外邊距和其第一個 in-flow 孩子節點的上外邊距
    • 盒子的下外邊距與其下一個緊挨著的in-flow中的兄弟節點的上外邊距
    • 盒子的下外邊距(並且該盒子的height computed value為auto)與其最後一個in-flow孩子節點的下外邊距
    • 盒子的上外邊距和下外邊距,滿足條件是:該盒子沒有建立新的block formatting modelmin-height為0,height的computed value為0或者auto,沒有in-flow 孩子節點。

另外需要注意的是摺疊外邊距也能夠與另一個外邊距相鄰,只需要其外邊距的任意一部分與另一個外邊距相鄰就算。

Note:由上面的collapse margin 的定義我們可以得出以下的推論:

  • 相鄰外邊距可以由不具備兄弟或祖先關係的元素之間生成
  • floated box的margin和任何其他盒子的margin都不會合並(包括它流內的孩子節點)- 因為 float box不是流內的。
  • 絕對定位的盒子的margin和任何其他盒子的margin都不會合並(包括它流內的孩子節點) - 同樣是因為絕對定位的盒子是流外的。
  • 建立新的block formatting context的元素的margin不會與它的流內孩子節點的margin合併(比如floated box)
  • inline-block盒子的margin不會與其他盒子的margin合併(包括它的流內孩子節點)- 建立了新的block formatting model並且不是block-level box.
  • in-flow block-level box的下外邊距會與相鄰的in-flow block-level兄弟節點的上外邊距合併,除非該兄弟節點存在clearance
  • in-flow block element的上外邊距會與它的第一個流內孩子節點(該孩子節點是block-level盒子)的上外邊距合併,這裡需要該元素不存在top border,不存在top padding,並且孩子節點也沒有clearance
  • 一個heightauto並且min-height0in-flow block-level boxbottom margin會與它的最後一個流內block-level的孩子節點的bottom margin合併,條件是該盒子沒有bottom paddingbottom border並且其孩子節點的bottom margin沒有與具有clearance的top margin合併
  • 盒子自身的外邊距也會合並,條件是min-height0, 既沒有上下邊框,也沒有上下內邊距,height為0或者auto,且不包含line box(並且所有流內孩子的外邊距都會合並)

那麼合併後的margin該如何取值呢?

  • 當兩個或者更多的margin合併時,collapsed margin的寬度為被合併的所有外邊距中的最大值
  • 如果存在負margin,那麼就從正相鄰margin中的最大值減去負相鄰margin中絕對值最大的值
  • 如果沒有正margin,就用0減去相鄰margin中絕對值最大的值

下面我們討論最後一種特殊情況: 如果一個盒子的上下外邊距相鄰,那麼有可能外邊距合併會穿透該盒子。在這種情況下,該元素的位置取決於合併margin的其他元素之間的關係:

  • 如果該元素的margins和它父元素的top margin合併,那麼該盒子的top border edge和它父元素的top border edge一樣
  • 否則的話,要麼該元素的父元素沒有參與margin collapse,要麼只有該父元素的bottom margin參與。那麼該元素的上邊框edge和該元素bottom border非0時相同。

需要注意的是,被摺疊外邊距穿過的元素的位置不影響其他外邊距正要被合併的元素的位置,其上邊框邊界的位置僅僅是用於佈局這些元素的後代元素。

padding屬性: padding-top, padding-right, padding-bottom, padding-leftpadding

padding屬性定義了盒子的內邊距區域的寬度。

'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
Value:  	`<padding-width>` | inherit
Initial:  	0
Applies to:  	除table-row-group,table-header-group,table-footer-group,table-row,table-column-group和table-column外的所有元素
Inherited:  	no
Percentages:  	參照包含塊的寬度
Media:  	visual
Computed value:  	指定的百分比或者絕對長度
複製程式碼

padding屬性和margin屬性主要由下面幾個不同:

  1. padding的取值沒有auto
  2. padding值不可以為負
  3. 適用的元素範圍不一樣

和margin一樣的是,百分比的基數是generated box's containing blockwidth(也就是該元素的包含塊的width).

note:這裡的padding-top等4個屬性仍然是physical 屬性。在CSS3當中新增了logical padding - padding-block-start等。

border屬性

'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
Value:  	`<border-width>` | inherit
Initial:  	medium
Applies to:  	所有元素
Inherited:  	no
Percentages:  	N/A
Media:  	visual
Computed value:  	絕對長度,或者'0',如果border style為'none'或者'hidden'的話
複製程式碼

對於border-width需要注意:

  1. <border-width>只有關鍵字和<length>這兩種選擇,關鍵字是thin, medium, thick,大小依次變大,具體大小由使用者代理決定
  2. 邊框寬度不能為負
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'
Value:  	`<color>` | transparent | inherit
Initial:  	'color'屬性的值
Applies to:  	所有元素
Inherited:  	no
Percentages:  	N/A
Media:  	visual
Computed value:  	從'color'屬性取值的話,取'color'的計算值,否則,就按指定值
複製程式碼

這裡需要注意的是: border-color 的初始值是 color 屬性的值,我們有些時候可以利用這個特性來實現一些特殊的要求

雙向環境(bidirectional context)中inline-level 元素的盒模型

對於每一個line box, UAs必須為每一個元素產生的inline box渲染margin,border,padding以visual order的方式(而非logical order) 主要分為兩種情況:

  • direction: ltr : 元素出現的第一個line box的最左端生成的盒子具有左外邊距,左邊框,左內邊距,並且元素出現的最後一個line box最右端生成的盒子具有右內邊距,有邊框,右外邊距
  • direction: rtl:元素出現的第一個line box的最右端生成的盒子具有右內邊距,有邊框,右外邊距,並且元素出現的最後一個line box的最左端生成的盒子具有左外邊距,左邊框,左內邊距

reference

  1. CSS Box Model Module Level 3
  2. CSS2.1 box model
  3. CSS Display Module Level 3
  4. CSS Flexible Box Layout Module Level 1

相關文章