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

前言

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

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

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

包含塊(containing block)

筆者簡單拋出幾個問題,包含塊定義是什麼?創建包含塊的元素必定是塊級元素嗎?包含塊區域是由祖先元素的內容邊界(content edge)仍是內邊距邊界(padding edge)構成?佈局

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

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.spa

簡單翻譯,「CSS 2.1中,不少盒的位置和大小是根據被稱爲包含塊的矩形框的邊計算的。通常把生成的盒做爲後代盒的包含塊,咱們說一個盒爲其後代「創建」了包含塊。.net

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

containing-block-details

稍做梳理,要點以下:3d

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

  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/…

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

相關文章
相關標籤/搜索