再讀規範中浮動與定位細節

程式碼君的自白發表於2019-02-25

前言

如題,浮動與定位是CSS佈局中重要且基礎知識點,相關規範和書籍都有篇幅解讀。但具體細節,筆者初讀囫圇吞棗,幾經再讀受益匪淺。感嘆規範,簡明扼要,字字珠璣 。藉此行文,著筆細節,竭盡所能,力求清晰。不過刪繁就簡,仍然又臭又長,自嘲廁所文。

在正文開始前,說幾句題外話。以CSS 定位為例,瀏覽器搜關鍵字,點選閱讀相關博文,你會發現大多通篇下來只是簡單描述 position 屬性值,及某種定位規則。其中對包含塊(containing block)細節描述幾近為零。如若對定位理解僅止步於此心滿意足,一味追逐新技術好高騖遠,豈不撿了芝麻丟了西瓜,得不償失。

本文參考 CSS 2.1 規範,主要闡述浮動(float)與定位(position)相關細節,例如包含塊定義、浮動細節、三列布局、清除浮動等。如有興趣,煩請平心,慢慢閱讀。

包含塊(containing block)

筆者簡單丟擲幾個問題,包含塊定義是什麼?建立包含塊的元素一定是塊級元素嗎?包含塊區域是由祖先元素的內容邊界(content edge)還是內邊距邊界(padding edge)構成?

首先來看 CSS 2.1 規範對包含塊(containing block)描述:

In CSS 2.1, many box positions and sizes are calculated with respect to the edges of a rectangular box called a containing block. In general, generated boxes act as containing blocks for descendant boxes; we say that a box "establishes" the containing block for its descendants.

簡單翻譯,“CSS 2.1中,很多盒的位置和大小是根據被稱為包含塊的矩形框的邊計算的。一般把生成的盒作為後代盒的包含塊,我們說一個盒為其後代“建立”了包含塊。

通俗來講,包含塊(containing block)用來確定元素生成盒位置和大小。比如當元素指定 position 為除 static 外的值時,其偏移屬性(right、top...)百分比是相對於包含塊的寬度或高度計算。參照規範 10.1 Definition of containing block 章節,為節省篇幅,具體規則如圖所示。

containing-block-details

稍作梳理,要點如下:

  1. 根元素包含塊稱為初始包含塊,對於連續媒體為視口(viewport)尺寸,分頁媒體是頁區(page area)尺寸。

  2. 非根元素且 position 值是 staticrelative,包含塊由最近的塊容器block container )祖先盒的 內容邊界(content edge)形成。

  3. 如果元素 position: fixed,包含塊是由連續媒體的視口或分頁媒體的頁區建立。

  4. 如果元素 position: absolute,包含塊由最近的 positionrelativeabsolute 或者 fixed祖先 (注:未限定是塊容器)建立。區分以下情況:

    1. 如果祖先是行內元素,包含塊是由該元素生成的第一個和最後一個行內盒的 內邊距盒(padding box)形成。

    2. 否則,包含塊由該祖先的 內邊距邊界(padding edge)形成。

根元素和元素為 position: fixed 情況較為簡單,前者為後者生成初始包含塊。細節在於 static 或 relativeabsolute 兩者差異。static 或 relative 元素包含塊為最近的 塊容器 祖先盒的 content edgeabsolute 元素包含塊為最近的 非 static 祖先盒padding edgepadding box ,再有 absolute 並未限定祖先為行內盒或塊容器盒

對於浮動(float)元素,其包含塊是其最近的塊級祖先元素。(參照《CSS 權威指南》浮動與定位章節)

包含塊(containing block)相關在工作中經常用到,細節熟稔於心才會從容自如。另外前面提及塊容器(block container box)的概念以及與塊級盒(block-level box)、塊盒(block box)的區別可查閱 9.2.1 Block-level elements and block boxes 章節。

浮動理論

浮動(float)最有意思的特性是其他內容會沿著它的一側排列。浮動元素會從文件的正常流中刪除,不過還是會影響佈局。例如圖片浮動時,其他內容會“環繞”該元素。

浮動盒之前或之後建立的未定位的(non-positioned)塊盒會正常排列,但接著浮動盒建立的當前及後續行盒會進行必要縮短,給浮動盒的 margin box 讓出空間。位於浮動盒之前的當前行盒裡的任何內容都會在浮動盒的另一側的相同行重新排列reflowed)。

再有浮動元素會為其內容建立一個新的塊級格式化上下文block formatting contexts)。表格,塊級可替換元素或者常規流中建立新的塊級格式化上下文的元素(例如一個overflow不為visible的元素)不能與同塊級格式化上下文中的任何浮動盒的 margin box 重疊。

提一個問題,當浮動與正常流中的內容發生重疊會怎麼樣?比如浮動元素存在負外邊距則有可能產生重疊。對此 CSS 規範指出明確規則:

  • 行內盒與一個浮動元素重疊時,其邊框、背景和內容都在該浮動元素“之上”顯示

  • 塊盒與一個浮動元素重疊時,其邊框和背景在該浮動元素“之下”顯示,而內容在浮動元素“之上”顯示

參考示例: codepen.io/sunpeijun/p…

此外,規範制定詳細規則,用來控制浮動行為。如圖所示,文字較多,如有興趣,自行查閱。

浮動規則

上述擷取自 CSS 2.1 規範 9.5.1 Positioning the float: the float property 章節。筆墨簡短,卻層次分明。簡述三兩條,左浮動盒左外邊界或右外邊界不能超出包含塊的左邊界或上邊界,多個浮動元素間不會相互重疊等。建議閱讀《CSS 權威指南》“第10章 浮動和定位”,有對每條規則詳細解讀。

再談三欄佈局

其實空讀理論,猶如紙上談兵。藉著三欄佈局場景,闡述 float 實現細節。首先限定佈局結構:左中右三欄佈局,左右兩欄寬度固定,中間欄寬度自適應。以筆者所熟知的三欄佈局,採用 float 實現可有兩種方式:左右兩欄浮動、三欄全浮動。

左右兩欄浮動

<!-- 結構 -->
<div class="left"></div>
<div class="right"></div>
<div class="main"></div>
複製程式碼
/* 樣式 */
 html, body { margin: 0; height: 100%; }
.main { height: 100%; margin: 0 210px; background: #ffe6b8; }
.left, .right { width: 200px; height: 100%; background: #a0b3d6; }
.left { float: left; }
.right { float: right; }
複製程式碼

參考示例: codepen.io/sunpeijun/p…

此種方式關鍵在於把主體標籤放置最後,左右兩欄 div 順序任意。筆者曾嘗試調整 main 元素順序,均實驗未果。那為什麼一定要將主體標籤放置最後?

規範 9.5 Floats 章節,存在一行文字:

If there is not enough horizontal room for the float, it is shifted downward until either it fits or there are no more floats present.

大意,“如果沒有足夠的水平空間來浮動,它會向下移動,直到空間合適或者不會再出現其它浮動”。結合塊級元素會佔滿整行('margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = 包含塊的寬度),後置位浮動元素在水平方向沒有足夠空間,所以不會漂浮上去。假如浮動元素前置脫離正常流,塊級元素以浮動元素不存在正常排列。佐以規範輔證:

Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float did not exist.

注意 main 塊盒也可用 overflow: hidden 替代 margin: 0 210px。如上節所述,overflow 不為 visible 的塊盒會建立新的塊級格式化上下文(BFC),同時不能與自身處於同一塊格式化上下文中的任何浮動盒的 margin box 重疊。

注:上文引用自規範 9.5 Floats

三欄全浮動

三欄全浮動,有聖盃佈局或雙飛翼佈局兩種經典方式。當然實際為實現三欄佈局採用上述,難免會有些脫離時代發展。使用絕對定位 或 flex佈局方式,更優雅且易於理解。這裡以雙飛翼佈局為例,解釋其 float 實現細節。

<!-- 結構 -->
<div class="main">
  <div class="container"></div>
</div>
<div class="left"></div>
<div class="right"></div>
複製程式碼
/* 樣式 */
 html, body { margin: 0; height: 100%; }
.main { float: left; width: 100%; height: 100%; }
.main .container { margin: 0 210px; height: 100%; background: #ffe6b8; }
.left, .right { float: left; width: 200px; height: 100%; background: #a0b3d6; }
.left { margin-left: -100%; }
.right { margin-left: -200px; }
複製程式碼

參考示例: codepen.io/sunpeijun/p…

需要注意,三欄全浮動方式主體元素順序不再是重點,主體元素可前可中可後(樣式需要微調)。筆者這裡想表達,為什麼左側元素需要 margin-left: -100%,右側元素要 margin-left: -200px

首先,兩側如果不採用負外邊距會放置主體元素下方,因為沒有足夠的水平空間與主體並排。但兩欄浮動元素設定足夠負外邊距,則可以使其移動至上方。橫向對比,行內非替換元素也可以使用 marign-left 負外邊距移動至上方行內盒區域(示例 codepen.io/sunpeijun/p…)。相反塊級元素、行內替換元素不可使用 margin-left 負外邊距使其產生垂直移動。

參考行內非替換元素特性,行內替換元素可以實現自動換行,例如文字過長會折行顯示,同時浮動元素也可在水平空間不足情形,會自動向下移動尋找合適空間。如此類比,大概可描述多個浮動元素構成一個浮動流,內部可使用 margin 調整其位置。

宣告: 以上解釋筆者未找到權威資料佐證,如有錯誤,敬請指正。

至於具體負外邊距值計算,可想象全部浮動元素水平排列。左欄元素浮動到目標區域需要往左移動整個包含塊的寬度,也就是 margin-left: -100% 。右欄元素為什麼要 margin-left: -200px?同理,左側欄往上移動目標區域後,右欄正常會在主體元素下方,靠近包含塊左邊。按上述思路,只需設定自身寬度的負外邊距即可放置右側目標區域。

此外將主體元素放置在左右兩欄中間,需要為 .main 新增 margin-left: -200px;,同時 .left 不在需要 margin-left: -100%;。示例見:codepen.io/sunpeijun/p…。主體元素放置最後情形,示例見: codepen.io/sunpeijun/p…

清除浮動

有時浮動元素會導致父元素高度坍塌,例如父元素中子元素浮動,並且沒有設定高度。那如何防止高度塌陷?先列出相關理論,讀後應該會有思路。

浮動,絕對定位元素,非塊盒的塊容器(例如,inline-blockstable-cellstable-captions)和 overflow 不為 visible 的塊盒會為它們的內容建立一個新的塊級格式化上下文。

一個表格,塊級可替換元素或者常規流中建立了新的塊格式化上下文的元素(例如一個 overflow 不為 visible 的元素)不能和與元素自身處於同一塊格式化上下文中的任何浮動盒的 margin box 重疊。

筆者偷個懶,簡單列出幾種方式,請參考上述理論自行理解。

  • 新增額外標籤設定 clear: both

  • 偽元素設定 clear: both

  • 包含塊設定 overflow 不為 visible

  • 包含塊浮動

  • 包含塊絕對定位

display、position、float之間的關係

displaypositionfloat 屬性相互影響盒的生成及佈局,順道說下三者關係。參照規範 9.7 Relationships between display, position, and float 章。

再讀規範中浮動與定位細節

  1. 如果 display 值為 none,那麼 positionfloat 不會生效。此時元素不生成盒。

  2. 否則如果 position 值為 absolute || fixed,盒為絕對定位且 float 的計算值為 nonedisplay 根據下表來設定。

  3. 否則如果 float 值不為 none,那麼盒浮動且 display 根據下表來設定。

  4. 否則如果該元素是根元素,display 根據下表來設定。

  5. 否則其它 display 屬性值(計算值)就用指定值

指定值 計算值
inline-table table
table-row, table-row-group, table-column, table-column-group block
table-header-group, table-footer-group, table-cell, table-caption block
inline, inline-block block

結語

或許本文過於抓細節,鑽牛角尖。但筆者認為,就是這些細小的知識點匯聚為技術道路上的絆腳石。腳踏實地沉澱每一個知識點,不積細流,無以成江河。

筆者不才,詳讀文件多日,將自認為重要且容易忽視細節整理成文。事實上浮動與定位依然還有很多知識點,只不過本文未能有所體現。倉促行文,脈絡凌亂。對於文中未闡述清楚或表達有誤之處,歡迎斧正,拱手謝過。如有困擾之處,可留言給筆者。

參考文件

CSS 2.1: www.w3.org/TR/2011/REC…

[譯]CSS 2.1: www.ayqy.net/doc/css2-1/…

再讀規範中浮動與定位細節

打個廣告,歡迎關注筆者公眾號

相關文章