1. 前言
前端圈有個“梗”:在面試時,問個css的position屬效能刷掉一半人,其中不乏工作四五年的同學。在公司一直有參與前端的基礎面試,深感這個“梗”不是個玩笑。
然而,我覺得實際比例可能會更高,甚至很多面試官自己也未必真正掌握。因為大部分前端同學,可能不知道初始包含塊的概念,或知道但對這個概念理解有誤。
造成這種現象的原因主要有兩方面,一方面是在介紹這個知識點時,網上有謬誤的文章太多,國內外亦如此(MDN也名列其中),導致很多同學被誤導(我一開始也是),而且這種錯誤被代代相傳;另一方面可能是我們平時不太注重概念的定義、自身對待知識的態度還不夠嚴謹、缺乏驗證精神和系統總結的習慣。
一次偶然的機會,我發現了這種謬誤,並找到了W3C組織對初始化包含塊的官方定義,也為了讓剛入前端圈的同學少走一些彎路,在此我想借本文分享給大家(詳述請見5.5. 包含塊章節),也系統分享一下,本人在前端佈局基礎方面積累的淺薄經驗。(因為是系統概述,所以篇幅會比較長,希望各位讀者有心理準備)
2. 什麼是前端佈局基礎?
前端佈局方案主要有三種:
- 傳統佈局方案(藉助浮動、定位等手段)
- flex佈局方案
- grid佈局方案
這些方案都能夠解決佈局問題,而且每個方案都有各自的理論基礎,那麼哪一個方案的基礎理論可以稱得上是前端佈局基礎?要回答這個問題,我們還得深入去了解這三種方案的特性。
傳統佈局方案,需要使用者熟練掌握元素的分類及佈局特性、浮動原理和定位原理等眾多基礎知識,方能在解決各類前端佈局問題時遊刃有餘,這不僅學習成本大,而且實現的複雜度也高,實現的CSS程式碼也不夠精簡、優雅。但由於其基礎知識來源於CSS2,所以瀏覽器相容性最好,對於使用者是友好的。
flex佈局方案,正是為了解決傳統佈局方案的種種不便,而提出的一種新型改進方案,它不再需要藉助浮動和定位等佈局手段,而是通過父元素(flex box)單方面配置相關的CSS屬性來決定子元素的佈局規則,且在大多情況下無需子元素(flex item)參與,就能完成子元素間的佈局問題,不僅學習成本低(公司之前有幾個後端工程師亦能快速上手),且大大簡化了佈局的實現複雜度,CSS程式碼也更加精煉。美中不足的是IE10才開始支援,且需要使用-ms-字首(IE11無需)。
雖然現今的手機多使用的是現代瀏覽器,對flex支援度較好,然而並不是每一款手機都如此:筆者曾在一個移動端專案採用過flex佈局方案,然而公司的測試同學在“華為榮耀5”的自帶瀏覽器,檢測到無法支援flex佈局,我們能夠跟測試的同學說,是這款華為手機的瀏覽器有問題嗎?顯然不能。於是故筆者在專案早期就及時放棄了flex佈局方案,改用傳統佈局方案實現,避免了後面大規模的改動。
grid佈局方案,是由微軟提出,相對於傳統佈局方案和flex佈局方案,它是一種二維佈局方案,在IE10開始支援,但需要使用-ms-字尾(IE11+不再需要)。
總的來說,這三類方案都能基本解決日常的前端佈局問題,且從易用性、靈活性和強大性來說,flex佈局和grid佈局更是未來的趨勢。但是從當前各版本瀏覽器在使用者市場上的使用情況和各方案的瀏覽器相容性來看,傳統佈局方案對使用者最友好,具有一定的不可替代性,所以我覺得,傳統佈局方案是最應該先掌握好的,尤其是對於在to B企業工作的前端同學來說。
所以本文將詳細介紹的“前端佈局基礎”,指的是圍繞著“傳統佈局方案”的眾多CSS知識,其主要內容來源於CSS2規範。
3. 為什麼要學好前端佈局基礎?
頁面寫多了的前端同學,我想應該都會有這樣一個深刻的感受:在編寫頁面時,經常會遇到不同場景的佈局問題,我們不僅需要針對特定的場景選定可實現的佈局實現方案,而且需要考慮未來可能發生的變化。
而要做好這一點,就需要紮實的前端基礎作為依託。
所以在我看來,學好前端佈局基礎,其目的是為了在面對不同場景的佈局問題時,能夠提出一種合理的佈局方案:既能解決問題,又能最大程度地擁抱變化。
4. 量化佈局方案的合理性
前面提到過的“解決問題”、“擁抱變化”,僅僅是合理佈局方案的兩大核心目標,如果想要讓目標更好地落地,我們仍需要一些量化合理性的原則,來提升對目標的方向感,以讓目標變得更加可執行。
說到量化“解決問題”這個目標,對於即寫即呈現的前端程式碼來說,我們可以很直觀地判斷一種方案是否可行,所以不需要太多的量化手段,我們主要是要量化“擁抱變化”這個目標。
要想量化“擁抱變化”這個目標,我們首先得清楚“變化”有哪些。筆者根據過往的開發經驗,將變化分為兩大類:一是佈局需求的變化,二是執行環境的變化。
而針這這兩類變化,我提出如下量化原則:
一、對於佈局需求的變化,可以做到:
- 方便快速定位需修改的位置
- 能夠不花或用最少的修改成本應對變化
二、對於執行環境的變化,可以做到:
- 在不同瀏覽器均有正確或良好的顯示
如果一個方案能夠體現以上幾點原則,我認為可以稱得上是一個合理的方案。最後,我將佈局實現方案的合理性歸納為:方案在滿足正確性的前提下,其實現邏輯規範、實現職責分明且擁有良好的瀏覽器相容性。
下面我們正式開始介紹與“傳統佈局方案”相關的佈局基礎知識。
5. 佈局基礎要點
5.1. CSS標準盒模型(或W3C盒模型)
一個web頁面是由眾多html元素拼湊而成的,而每一個html元素,都被解析為一個矩形盒,而CSS盒模型就是這種矩形盒的解構模型。CSS盒模型,它由內到外、被四條邊界Content edge、Padding edge、Border edge和Margin edge劃分為四個區域:Content area、Padding area、Border area和Margin area,在形狀上,Content area(又稱content-box)是實心矩形,其餘是空心環形(空心部分是Content area),如下圖所示:
CSS盒模型-區域劃分圖
此外,每個區域都有其特定的作用:Content area,是當前元素用來容納所有子孫元素;Padding area,是當前元素用來隔離自身和子孫元素;Border area是當前元素用來顯示自身的輪廓;Margin area,是當前元素用來隔離自身和相鄰元素。理解每個區域的作用和職責至關重要,有助於我們寫出優雅、清晰的佈局程式碼。
CSS盒模型-區域作用圖
而每個區域的尺寸,又分別由特定的CSS屬性來控制,如下圖所示:
CSS盒模型-屬性控制圖
這些CSS尺寸屬性(width、height、padding、border和margin),相當於一個個hook,我們可以通過設定這些“hook”來達到調整元素尺寸的目的。
5.2. box-sizing(CSS3屬性)
5.2.1. box-sizing的作用
box-sizing,顧名思義,其作用與設定CSS box的尺寸大小有關,而CSS box又可細分為:
- content-box(即content area)
- padding-box(=content area + padding area)
- border-box(=content area + padding area + border area)
- margin-box(=content area + padding area + border area + margin area)
簡單來說,box-sizing的作用就是告訴瀏覽器:CSS屬性width和height是用於設定哪一種box的尺寸,在W3C標準中,box-sizing的值僅有content-box和border-box(firefox則額外支援padding-box)。所以,
當box-sizing的值為content-box(預設值)時,有:
width = content-width;
height = content-height;
當box-sizing的值為border-box時,有:
width = content-width + padding-left + padding-right + border-left-width + border-right-width;
height = content-height + padding-top + padding-bottom + border-top-height + border-bottom-height;
關於box-sizing的作用,還有另一種表述:告訴瀏覽器,是使用W3C盒模型,還是使用IE盒模型。
5.2.2. box-sizing的瀏覽器相容性
box-sizing是CSS3屬性,在IE8+(包含IE8)開始支援,然而在IE8,box-sizing的值為border-box時,不能與min-width, max-width, min-height或max-height的一起使用,因為IE8對min-*和max-*的解析,仍是作用於content-box,不受box-sizing屬性控制。
5.2.3. box-sizing的產生原因
僅僅掌握box-sizing的基礎使用,是無法真正理解box-sizing的作用,所以要想把box-sizing用好,我們還得從CSS盒模型的發展史來深入理解box-sizing的產生原因。
在CSS的發展歷程中,有兩個版本,一個是IE盒模型,另外一個是W3C盒模型。IE盒模型,在IE5-(包含IE5)和navigator4上均有使用;而W3C盒模型,在IE6+(包含IE6)標準模式開始得到支援。兩種版本的盒模型,其實在模型結構上是一致的,只是with和height屬性的計算規則不一樣,其區別,等價於“box-sizing的兩個屬性值border-box和content-box的區別“,如下圖所示:
IE盒模型和W3C盒模型的區別
在瞭解了CSS盒模型的發展歷程,以及後來新增的box-sizing的開始支援時間,我們不難發現:
- IE5-採用IE盒模型
- IE6、7的標準模式放棄了IE盒模型,轉為使用W3C盒模型
- IE8+藉助box-sizing,又重新提供了對IE盒模型的支援
對於IE盒模型,我們看到了W3C組織先去後留的反覆態度,我不禁提出以下兩點疑惑:
問題一: 為什麼W3C組織在制定盒模型標準時,一開始會放棄IE盒模型,而重新建立以content-box為計算規則的W3C盒模型?W3C盒模型比IE盒模型好在哪裡?
問題二:為什麼在CSS3中,又重新提供了對IE盒模型的支援(box-sizing設定為border-box),又是基於哪方面的考慮?
關於第一個問題,本人並沒有找到相關的官方說明,但我比較認可的一種說法是:
在日常生活中,我們在放東西時,會關心東西放到多大的盒子裡面,這裡的“多大”,往往指的是盒子的容量,而不是整個盒子的尺寸。而HTML元素也被看成是一個盒子、一個容器,相應地,我們也會更關注其內容區域的尺寸,也更希望對內容區域有更強的控制力。所以,從儲存的角度來看,W3C盒模型更符合這種認知,藉助width和height,我們可以通過宣告的方式,直接設定conent-box的尺寸。而如果採用IE盒模型,我們只能先設定整個盒子的尺寸(border-box),最後由瀏覽器自動計算出content-box的尺寸,顯得對content-box尺寸的控制力較弱。
關於第二個問題,我認為有以下幾個原因:
1. 有助於複用基於IE盒模型開發的CSS程式碼;
2. IE盒模型的“遺老遺少”可以延續計算習慣;
3. 部分html元素,在解析時依然採用IE盒模型的計算規則(這樣的元素有select、button),使用IE盒模型有助於保持一致性;
4. 從元素佈局的角度來看,IE盒模型的width和height的語義更符合人類的直觀認知(盒子的尺寸、輪廓應該以border為界);
5. 在彈性佈局和響應式佈局場景,IE盒模型比W3C盒模型表現更佳(更容易實現、瀏覽器相容性更好),如設定某個元素的寬度始終佔當前行總寬度的固定百分比(小於100%),並且該元素擁有固定畫素的padding;
舉個例子:設定一個元素,其寬度分別為當前行的40%,且該元素的padding固定為10px。
IE盒模型的實現方案:
方案一: 使用一個div即可實現,直接設定width為40%,padding為10px;
W3C盒模型的實現方案:
方案一:使用兩個div模擬實現,外層div的width設定為40%,內層div的padding為10px, width為auto;
方案二:使用一個div即可實現,但是需要借用CSS3的calc函式,動態計算其內容區域的寬度,即width為calc(40% – 20px), padding為10px;
顯然,IE盒模型的實現方案更加簡潔,而且瀏覽器相容性更好。
對上述兩個問題的解答,其實也是對IE盒模型和W3C盒模型的一個比較。我們可以從比較中,明晰兩種盒模型各自的優缺點。同時,經過大量的實踐經驗證明和充分討論,IE盒模型總體上是優於W3C盒模型,這也是IE盒模型能夠“王者歸來”,被W3C組織重新啟用的真正原因。
於是乎,為了重新在新規範中支援IE盒模型,也為了向後相容W3C盒模型,W3C組織在CSS3中新增了box-sizing屬性,用於切換這兩種盒模型。
5.2.4. 對box-sizing的評價
在我看來,在CSS3中新增box-sizing其實是一種比較trick的彌補方式。雖然這種設計能重新提供對IE盒模型的支援,但是在某種程度上,造成了CSS屬性width和height具有二義性,使其職責變得不單一。然而這似乎又是最可取的修正方案了,因為在網上已經存在了大量基於W3C盒模型開發的網頁,後續的修正方案不得不考慮向後相容。我們只能在不合理設計的基礎上,再次用不優雅的設計來解決新的問題。
如果能夠穿越時空,回到W3C組織在討論“如何設計標準盒模型”時,我認為更合適的設計方案是新增新的屬性單獨用於設定content-box的尺寸,而保留IE盒模型width和height原來的語義。這樣就不會有後來的box-sizing屬性。
我猜想W3C組織也想過這種方案,但是當時可能認為:
1. 直接設定元素border-box尺寸的意義不大,且border-box的尺寸設定也能夠通過設定content-box的尺寸來實現;(其實同時兩種支援content-box和border-box尺寸的設定也無妨,完全可以當做是語法糖)
2. 設定content-box尺寸又屬於高頻操作,若新增的屬性命名為content-width或content-height則顯得名稱太長;(命名為cwidth和cheight也行)
基於這兩點,最終提出了用width和height來設定content-box尺寸的解決方案,也就是如今我們看到的W3C盒模型。
縱觀CSS盒模型的發展史和box-sizing的建立原因,感觸比較深的就是:不合理的設計並不是總會被修正,因為既有實現的廣泛應用,會使得其被繼續遵循。而後續的新增設計,也是建立在先前不合理設計的基礎上。這是否也驗證了黑格爾的哲學名言:存在即合理?
關於對box-sizing的評價和思考,可能顯得有一些馬後炮,一些猜想也可能只是筆者的憑空臆想,並非W3C組織原意。在這裡只是為了分享我對重構的一些思考,也是為了與和我有同樣疑惑的同學做個交流。
5.2.5. box-sizing的最佳實踐
在這裡主要回答三個問題:
問題一:box-sizing的值,取content-box好,還是取border-box值好?
如果最低需要相容IE6、7,那麼box-sizing不可使用,只能使用W3C盒模型;
如果最低只需相容IE8,那麼使用content-box在功能上完全沒有問題,只是在一些彈性佈局和響應式佈局實現上,會稍微麻煩一點;而border-box雖然在這些方面表現更好,但是不能和IE8的min-width、min-height、max-width和max-height四個屬性一同使用,使用的話就要稍微注意一下;
如果最低只需相容IE9,那麼本人覺得,全域性配置取content-box更為合適,區域性配置二者均可。原因如下:
1. CSS3提供了calc函式(IE9+),使得W3C盒模型有了強有力的助攻,在彈性佈局和響應式佈局的表現,與IE盒模型無異;
2. 預設優於配置原則:我個人認為,“預設優於配置”,特別是在reset.css這種架構級、平臺級的配置檔案,要儘量避免對未來可能引入的模組有侵入性。譬如,我們在一個專案中時常需要引入第三方元件,如果這個元件沒有強宣告box-sizing,那麼其預設使用的就是W3C標準盒模型,如果在全域性的reset.css中設定box-sizing的值為border-box以選用IE盒模型,那麼就會影響到這一類預設基於W3C盒模型的第三方元件的樣式。這裡也給我們提了一個醒,在封裝元件時,記得強宣告box-sizing,哪怕你使用預設的content-box。
總之,大部分場景二者可以互換,只是使用理念不一樣。小部分場景border-box更具優勢,但隨著calc函式的支援,這種優勢已經不再,相反content-box是預設值的優勢愈加明顯。
我個人建議是:全域性使用預設W3C盒模型(你的CSS程式碼最低能夠相容IE6/7,在IE8也可以和min-*和max-*一起使用),區域性場景二者均可(僅把IE盒模型當作是一種佈局技巧來使用)。你喜歡全域性使用IE盒模型也是可以的,只要確認專案只需要相容到IE8,即便有可能影響到引入的第三方元件,也是有辦法處理的。
問題二:如果想要全域性使用IE盒模型,那麼在reset.css中,該怎樣設定box-sizing?
這裡提供一個參考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
html { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } *, *:before, *:after { -webkit-box-sizing: inherit; -moz-box-sizing: inherit; box-sizing: inherit; } |
這樣設定的好處有:
1. 子元素的盒模型型別,預設由父元素決定,方便元件統一設定;
2. 支援低版本的瀏覽器:Safari (< 5.1), Chrome (< 10), and Firefox (< 29);
問題三:Bootstrap3開始,全域性使用IE盒模型(box-sizing取border-box),又是基於怎樣的考慮?怎麼協調好與基於標準盒模型開發的第三方元件的關係?
眾所周知,BS2還考慮對IE7的相容,而BS3徹底放棄了對IE7的相容,並將box-sizing設定為border-box。關於這一點,可見“Bootstrap 3 released”官方釋出的change list,摘錄如下:
從以上直白的表述中:Better box model by default(預設使用更好的盒模型),我們可以看出BS作者是IE盒模型的擁躉。作者也把理由羅列了出來,其核心內容也是如前面所提到的,IE盒模型在響應式佈局上的良好表現。補充的一點是,如果不全域性設定border-box,而每個元件及其子元件單獨設定,維護起來將是個夢魘(作者在官方編號為12351的issure中有提到)。
而關於BS如何處理好與基於標準盒模型開發的第三方元件的關係,亦可參見編號為12351的issue:”Move away from * {box-sizing: border-box } to play nice with 3rd party scripts”
作者在issue中,霸氣又委婉地回應:
1. BS並不考慮對第三方元件和框架的支援。作者委婉地說,BS是一個大專案,活躍維護者也主要是四個人,顧不來所有人的需求啊~(但感覺作者是在說,BS是個又大又全的框架,你丫還搞第三方元件幹嘛呀)
2.IE盒模型,用了大家都說好,為什麼第三方元件不轉過來支援IE盒模型啊(果然是鐵粉)
本章節從box-sizing的作用、瀏覽器相容性、產生原因、評價和最佳實踐這5個切入點,來講解box-sizing屬性,以期加深各位同學對這個屬性的理解和掌握。特別要強調的一點是,如果剛接手某個專案,在編寫CSS程式碼前,先看看專案是否有全域性配置box-sizing,並根據具體的取值來選用相應的尺寸計算規則。
5.3. 元素的分類及其佈局特性
5.3.1. 元素的分類
從元素的佈局特性來分,主要可以分為三類元素:block-level(塊級)元素、inline-level(行內級)元素和inline-block-level(行內塊級)元素,我們可以對其下個定義:
5.3.1.1. 塊級元素
display屬性取block、table、flex、grid和list-item等值的獨佔一行顯示的元素。
5.3.1.2. 行內級元素
display屬性取inline值的可在同一行內排列顯示的元素。
5.3.1.3. 行內塊級元素
display屬性取inline-block、inline-table、inline-flex和inline-grid等值的兼具塊級元素和行內級元素佈局特性的元素。
友情提示:
1)關於各類元素display的取值,實際已全部羅列,但為了保證定義能擁抱變化(未來可能引入新的display屬性值),在羅列時使用了等字;
2)w3c官方文件,把display屬性值為inline、inline-block、inline-table的元素,統稱為inline-level元素,我不太喜歡也不太認可這種泛泛的分類,本文重新定義了一個“inline-block-level元素”的概念,來對“inline-level元素”進行了細分,並將inline-blocks、inline-tables單獨分類為inline-block-level元素,原文件如下:“The following values of the ‘display’ property make an element inline-level: ‘inline’, ‘inline-table’, and ‘inline-block’.”
5.3.2. 元素的佈局特性
5.3.2.1. 塊級元素(block-level)的佈局特性
對於塊級元素,有如下幾個佈局特性:
- 獨佔一行(width預設為100%,height為0);
- 可以設定任何尺寸相關的屬性(width、padding、margin和border);
5.3.2.2. 行內級元素(inline-level)的佈局特性
在講行內級元素的佈局特性之前,我們先了解一下行內級元素的分類,其可再細分兩類元素:
1)可置換行內元素
在MDN中,其對“可置換行內元素”的定義如下:
按字面翻譯,“可置換行內元素”,是展示內容不在CSS作用域內的元素。這句話是不是不好理解?我們可以換另外一種方式理解:“可置換行內元素”,是這樣一類元素,其展示的內容是通過元素的src、value等屬性或CSS content屬性從外部引用得到的,可被替換的。隨著內容來源或內容數量的變化,可置換元素本身也會有水平和垂直方向上尺寸的變化。典型的可替換元素有 <img>
、<object> 、<video>
和 <embed>,表單類的可替換元素有 <textarea></textarea>和<input> ,某些元素只在一些特殊情況下表現為可替換元素,例如<audio>
、<object>、<canvas>
和<applet>。
特別地,通過 CSS content 屬性來插入的物件又被稱作 匿名可置換元素。
2)不可置換行內元素
“不可置換行內元素”其實就是我們常見的一類行內元素,這一類行內元素有<a>和<span>等。“不可置換行內元素”是相對於“可置換行內元素”的,其展示的內容是在CSS作用域範圍內的,是不可替換的。
言歸正傳,行內級元素有如下幾個佈局特性:
- 在一行內可以與多個同型別的元素按從左到右的順序排列;
- 不可置換行內元素不能設定width、height和垂直方向上的margin,而可置換行內元素則可以;
- 在水平和垂直方向上的對齊方式,行內級元素分別受父元素的text-align屬性和自身vertical-align屬性的控制(父元素是table-cell元素時,也受父元素的vertical-align屬性控制),在水平方向上預設左對齊,在垂直方向上預設在行框的baseline基線上顯示(“行框”的概念,會在後面深入講解);
友情提示:
1)有時候我們不必太糾結於哪些行內元素是可置換行內元素,因為有些瀏覽器(如chrome)的預設樣式(user agent stylesheet),會將這一類元素重置為inline-block元素,我們可以統一把可置換行內元素理解為inline-block元素,因為其佈局特性與inline-block-level元素相同。
2)當inline-level元素水平排列時,兩兩之間可能會出現大約6px的空白,這是由元素間的空白字元(換行符、空格或製表符)產生,如下圖所示:
清除方法有很多,本人習慣用浮動的方式來處理,其它方法可自行google。
5.3.2.3. 行內塊級元素(inline-block-level)的佈局特性
行內塊級元素兼具block-level元素和inline-level元素的佈局特性,主要體現為:
- 排列方式與行內級元素同,不獨佔一行,在一行內按從左到右的順序排列;
- 水平和垂直方向上的對齊方式與行內級元素同;
- 和塊級元素一樣,可以設定任何尺寸屬性(但width預設為0);
注:我們不難發現,其實可置換行內元素,其佈局特性與inline-block-level元素相同。
5.4. 格式化上下文(Formatting Context)
格式化上下文,它指的是具有某種CSS格式化規則(佈局規則)的上下文環境,在這個上下文環境內的所有子元素,都將根據其特定的CSS格式化規則來進行排列。
我們可以給某個作為容器的元素指定特定的格式化上下文,也就是說我們可以定義一個具有特定佈局規則的渲染區域。常見的格式化上下文有BFC(CSS2.1 規範)、IFC(CSS2.1 規範)、 FFC(CSS3規範新增)和GFC(CSS3規範新增),具體介紹如下:
5.4.1. BFC
5.4.1.1. 定義
BFC, 全稱是block formatting context,它是一個獨立封閉的渲染區域,在這個區域內的所有元素,從區域的頂部起,一個接一個地根據自身的佈局特性進行排列:在這個區域內的塊級元素 ,按從上到下的順序顯示,相鄰的塊級元素可以使用margin隔離,但在垂直方向上相鄰的塊級元素會發生margin合併;在這個區域內的inline-level或inline-level-block元素,則按從左到右的順序顯示(W3C組織說BFC內部的元素都是一個接一個地垂直顯示,我覺得不是很嚴格,因為BFC內部也可以容納inline-level和inline-level-block元素,所以這裡我的解釋和W3C還是稍微有一些不一樣)。具有BFC格式化環境的元素,我們稱之為BFC元素,可以說,BFC定義了BFC元素content區域的渲染規則。
看到這段描述,是不是覺得BFC的渲染規則,不就是文件流的預設佈局規則嗎?確實很像,但不完全等同。BFC元素內部的渲染規則和普通塊級元素內部的渲染規則,還是有一些不同的,我們將在5.4.1.3. 特性一節詳述。
5.4.1.2. 建立方式
建立BFC元素的方式有如下幾種(摘自MDN BFC):
- 根元素或其它包含它的元素
- 浮動元素 (元素的
float
不是none
) - 絕對定位元素 (元素的
position
為absolute
或fixed
) - 內聯塊 (元素具有
display
: inline-block
) - 表格單元格 (元素具有
display
: table-cell,HTML表格單元格預設屬性
) - 表格標題 (元素具有
display
: table-caption
, HTML表格標題預設屬性) overflow
值不為visible
的塊元素display
: flow-root
contain
為以下值的元素:layout
,content
, 或strict
- 彈性項 (
display
: flex
或inline-flex
元素的子元素) - 網格項 (
display
: grid
或inline-grid
元素的子元素) - 多列容器 (元素的
column-count
或column-width
不為auto
, 包括column-count: 1
的元素) column-span
: all
應當總是會建立一個新的格式化上下文,即便具有column-span: all
的元素並不被包裹在一個多列容器中。
5.4.1.3. 特性
BFC元素具有如下特性:
1. 對應一個獨立、封閉的渲染區域,子元素的CSS樣式不會影響BFC元素外部;
舉個例子,我們分別用連續的兩個塊級元素,一個是普通塊級元素,另一個是BFC元素(均使用綠色背景),分別包裹一個margin-top為20px的子元素(黃色背景),對比其佈局效果:
說明:
- 普通塊級元素,其子元素的margin-top,不會隔開自身與父元素(普通塊級元素),但是會作用到父元素外部(將父元素和叔伯元素或祖父元素隔開);
- BFC元素,作為一個獨立、封閉的渲染區域,其子元素的margin-top,則會隔開自身與父元素(BFC元素),而不會影響到父元素外部;
2. 浮動子元素參與BFC父元素的高度計算,也就是BFC元素能夠識別浮動元素(將元素宣告為BFC元素,也是clearfix解決父元素塌陷問題的一種常用方法);
舉個例子:
說明:
- BFC元素,能夠識別浮動子元素,浮動子元素參與BFC元素的高度計算,不會出現“高度塌陷”問題;
- 普通塊級元素,不能夠識別浮動子元素,會出現“高度塌陷”問題;
3. 佔據文件流的BFC元素(可使用overflow: auto建立),能夠識別浮動的兄弟元素;
舉個例子:
說明:
- 普通塊級元素,不能夠識別浮動的兄弟元素,會被浮動的兄弟元素覆蓋部分內容;
- 佔據文件流的BFC元素(可使用overflow: auto建立),能夠識別浮動的兄弟元素,不會被浮動的兄弟元素覆蓋,與之同行顯示;
4. 佔據文件流的BFC元素(可使用overflow: auto建立),width為auto時,會佔滿當前行的剩餘寬度;
舉個例子:
說明:
- 文件流中的BFC元素, width為auto時,會佔滿當前行的剩餘寬度;
5.4.2. IFC
5.4.2.1. 定義
IFC, 全稱是inline formatting context,其內部的元素,在水平方向上,一個接一個地顯示;在垂直方向上,每個元素可以設定不同的對齊方式;IFC內部的元素,被一行行的矩形框所包含,這些虛擬的矩形框,我們稱為行框(line box)。IFC的作用區域,可以看成是包含其所有子元素的行框組成的矩形區域。
5.4.2.2. 建立方式
和BFC相比,它的建立方式是被動的、隱式的,是由所包含的子元素來建立:只有在一個區域內僅包含可水平排列的元素時才會生成,這些子元素可以是文字、inline-level元素或inline-block-level元素。
5.4.2.3. 特性
1. IFC內部的元素,按從左到右、從上到下的順序排布;
2. IFC內部的每個元素,都可以通過設定vertical-align屬性,來調整在垂直方向上的對齊;
3. 包含這些內部元素的矩形區域,形成的每一行,被稱為line box(行框,後面會詳細介紹);
5.4.3. FFC和GFC
FFC(flex formatting context)和GFC(grid formatting context),分別是flex佈局和grid佈局的內容,這兩個模組的內容非本文介紹的重點,所以感興趣的同學可以自行google。
5.5. 包含塊(Containing Block)
5.5.1. 定義
我們在設定元素尺寸屬性(width、height、padding、margin和border)的百分比值或偏移屬性(top、right、bottom和left)的值時,通常會有一個“相對參考系”,這個”相對參考系”一般是包裹著這個元素的塊級祖先元素(一般是塊級父元素)或離這個元素最近的非static(relative、absolute和fixed)定位的祖先元素。這些具有“相對參考系”作用的祖先元素,其容納區域(cotent box或padding box),其實還有一個專門術語形容之,那就是包含塊(在知識體系中有個包含塊的概念,有助於加深對position定位原理的掌握)。
特別地,relative定位元素,其尺寸屬性(width、height等)的“相對座標系”仍是其包含塊(塊級祖先元素(一般是父元素)的content box),但是偏移屬性(top、right、bottom和left)的“相對座標系”則是其在文件流原來的位置。
5.5.2. ICB(initial containing block, 初始包含塊)
5.5.2.1. 定義
如前面所說,任何一個元素都會有一個包含塊作為設定尺寸屬性和偏移屬性的“相對參考系”,而對於頂層的根元素<html/>,沒有任何元素包裹它,它的包含塊是什麼?它選取什麼作為“相對參考系”?
其實根元素<html/>是有包含塊的,它是一個不可見的矩形框,W3C組織稱之為ICB(initial containing block, 初始包含塊)。以下是W3C組織對ICB對定義:
The containing block in which the root element lives is a rectangle called the initial containing block.
5.5.2.2. ICB的尺寸和起始位置(左上角座標)
在解釋ICB的尺寸和起始位置時,在這裡先簡單補充一個背景知識:連續媒體(continuous media)和分頁媒體(paged media)。如何理解這兩個概念?在視覺閱讀層面,它們是展示內容的兩種呈現方式。
連續媒體,就是採用連續展示內容的方式,它保持了展示內容顯示的連續性(一頁顯示所有內容),我們可以在連續媒體的viewport(可視視窗)檢視當前呈現的內容。特別地,瀏覽器視窗就可以看成是連續媒體,當內容的尺寸超過viewport時,讀者可以通過平滑滾動的方式來閱讀內容。
分頁媒體,就是採用切頁展示內容的方式,它將要展示的內容切分為等尺寸的多頁(分頁顯示所有內容),我們可以在分頁媒體的page area(頁面顯示區域)檢視當前呈現的內容。特別地,像幻燈片、電子書閱讀器,就可以看成是分頁媒體,當內容的尺寸超過page area時,讀者可以通過切頁的方式來閱讀內容;
對於屬於連續媒體(continuous media)的瀏覽器視窗來說,ICB的尺寸為viewport(瀏覽器視窗),其起始位置為畫布原點(canvas origin,即首屏的左上角,瀏覽器渲染資料後生成的內容文件可以看成是一張畫布)。
對於分頁媒體來說,ICB的尺寸為page area(關於ICB在分頁媒體的起始位置,沒有找到相關資料,但這個對於本文來說也不是重點)。
直觀來看,根元素<html/>的包含塊ICB,就是“首屏”。
5.5.3. 不同定位元素分別對應的包含塊
- static和relative定位元素的包含塊,為其塊級祖先元素(通常是塊級父元素)的content box;
- absolute定位元素的包含塊,為最近的非靜態定位祖先元素的padding box,查無非靜態定位祖先元素,那麼它的包含塊是ICB(即根元素<html/>的包含塊);
- fix定位元素的包含塊,為當前viewport(視窗);
在這裡要強調的一點,ICB(初始包含塊)是專有名詞,它特指根元素<html/>的包含塊。不要將一個元素的初始包含塊,錯誤理解為它的父元素。MDN的一位編輯者也犯了這種錯誤。具體如下:
經修正後:
也有一些權威CSS書籍說,當一個絕對定位元素找不到最近的非static祖先元素時,則相對於根元素<html/>定位,這種說法也是不嚴謹的。剛好看到一本,如下:
圖1. 書的封面
圖2. 原話截圖
我們可以通過一個簡單的例子推翻這種說法:將根元素html的高度設定為超過viewport高度,如5000px(假設viewport高度為500px),再將一個沒有最近的非static祖先元素的絕對定位元素的bottom設定為0,尺寸為100px*100px即可。如果真如該書中所言,那麼在首屏時,該絕對定位元素是被隱藏在滾動條下面的。而實際情況是:該絕對定位元素必然出現在首屏的底端,並且會隨著頁面滾動而滾動。驗證如下:
相信這個謬誤在前端圈流傳已久,希望各位同學引起重視。
5.6. 基本原理
5.6.1. 文件流(正常流)
5.6.1.1. 定義
關於“文件流”,並沒有找到較為官方的定義。筆者從google搜到一些認為比較靠譜的解釋,羅列如下:
1)摘自:《CSS: understanding the document flow》
The document flow is the model by which elements are rendered by default in the CSS specifications. In this model, elements are rendered according by their default display rule. In other words, block-level elements are displayed on a new line and inline elements on the same line. Everything is stacked in an ordered way from top to bottom.
2)摘自:《What is “document flow”?》
Document flow is the arrangement of page elements as defined by positioning statements and the order of html statements; that is, how the different elements take up space and arrange themselves around each other.
在這裡我想分享一下我自己對“文件流”下的定義:
文件流,是頁面元素預設存放的“容器”。
5.6.1.2. 特性
文件流具有如下特性:
1. 文件流按照頁面元素書寫的順序,將頁面元素按從左到右,從上至下的一般順序進行排列,而頁面元素則根據自身的佈局屬性(block-box or inline-box),決定是行內顯示,還是換行顯示;
2. 文件流內的元素,相互尊重:有序排列,彼此識別;
5.6.1.3. 脫離文件流
元素脫離文件流,按我之前下的定義,其實就意味著:元素脫離了預設存放的容器,換到另外一個容器存放。一個元素脫離了文件流,這樣會導致:其父元素無法識別其,其也不參與父元素高度的計算。若有一個父元素的所有子元素都脫離文件流,則會出現“高度塌陷”問題。常見的脫離文件流的方法有:
- 將元素設定為浮動元素
- 將元素設定為absolute、fixed元素
5.6.2. 浮動(float屬性)
5.6.2.1. 浮動元素的分類
根據float屬性的設定,元素可以分為浮動元素(值為left或right)和非浮動元素(值為none)。而按浮動方向劃分,又可細分為:
- 左浮動元素:float值為left的元素
- 右浮動元素:float值為right的元素
5.6.2.2. 浮動原理
要想掌握浮動元素的浮動原理,只要理解浮動元素的浮動起始位置、浮動方向和浮動結束位置即可。
- 浮動起始位置浮動元素(包括左右)的浮動起始位置,為最後一行最左側的空白位置,而不管空白位置是否能夠容納當前浮動元素;
- 浮動方向左浮動元素的浮動方向為從起始位置向左浮動;右浮動元素的浮動方向為從起始位置向右浮動;
- 浮動結束位置
左浮動元素遇到第一個左浮動元素或包含塊的最左側padding時,結束浮動;
右浮動元素遇到第一個右浮動元素或包含塊的最右側padding時,結束浮動;
以下demo可以幫助各位同學理解浮動元素的三要素:
/06:layout/float/1. 浮動元素三要素.html:
1 2 3 4 5 6 7 8 9 10 11 |
<body> <div class="fl">左浮動元素-1(width: 30%; height: 100px;)</div> <div class="fl">左浮動元素-2(width: 30%; height: 200px;)</div> <div class="fl">左浮動元素-3(width: 30%; height: 100px;)</div> <div class="fl">左浮動元素-4(width: 30%; height: 100px;)</div> </body> |
顯示結果:
說明:
a. 有四個連續左浮動的元素,每個元素寬度為30%;
b. 當一行排滿三個元素時,當前行只剩10%的寬度,不足以容納第四個左浮動元素;
c. 第四個浮動元素,從起始位置(最後一行的最左側空白)開始向左浮動,直到遇到第二個浮動元素的邊界;
為了幫助大家理解好浮動原理,在這裡我想額外定義幾個術語:
- 左浮動隊:由若干個連續的左浮動元素組成
- 右浮動隊:由若干個連續的右浮動元素組成
- 左浮動隊頭元素:左浮動隊的第一個元素,也是最左側的元素
- 右浮動隊頭元素:右浮動隊的第一個元素,也是最右側的元素
特別地,
1. 同一行內,最多有兩條浮動隊,一是左浮動隊,二是右浮動隊;
2. 同一行內,一條浮動隊可能佔滿一行;
3. 連續浮動的若干元素,如果無法在同一行內顯示,則會按行被切分為兩條或更多條浮動隊;
5.6.2.3. 浮動對元素display的影響
當元素設定為浮動元素後,可能會引發display屬性的值變化,具體規則如下:
浮動元素display屬性變化對照表
5.6.3. 清除浮動(clear屬性)
5.6.3.1. 三要素
清除浮動,其作用是改變“當前元素”與“前一個宣告的浮動元素”之間的預設佈局規則,這種改變主要體現為:讓當前元素換行顯示。這句話包含三個要素,分別為:
- 使用者:當前元素(浮動元素或者非浮動元素的塊級元素)
- 作用物件(清除誰的浮動):前一個宣告的浮動元素
- 目的(作用):讓當前元素換行顯示
特別地,為什麼使用者不包括非浮動的inline元素?因為非浮動的inline元素能夠識別浮動元素,是否使用clear清除“前一個宣告的浮動元素”的浮動,其佈局結果是一樣的。感興趣的同學可以參考:06:layout/clear/4.非浮動inline元素清除左浮動.html,可以在偵錯程式中觀察註釋非浮動inline元素的clear:left前後,其顯示位置的變化。而非浮動的塊級元素,因為無法識別前面宣告的左浮動元素,故會和左浮動元素髮生重疊(左浮動元素在上),所以非浮動的塊級元素使用clear:left清除前一個左浮動元素,就能避免重疊的現象。
5.6.3.2. clear屬性的取值及應用場景
前面簡單介紹了clear屬性的作用,是清除前面宣告的浮動元素的浮動,然後讓當前元素換行顯示。但是要具體怎麼使用,我們還得深入到clear的屬性值和應用場景。
clear屬性的取值有left、right和both。那麼它們的應用場景分別是什麼?
left值的應用場景是,前面宣告的浮動元素是向左浮動(float: left);
right的應用場景是,前面宣告的浮動元素是向右浮動(float: right);
both的應用場景是,前面宣告的浮動元素的浮動方向不確定,可能是左,也可能是右(瞭解過clearfix實現原理的同學,就不難明白);
再次強調一下,當前元素如果要清除浮動,清除的是前面宣告的浮動元素的浮動,其clear屬性要取什麼值,跟當前元素的是否是浮動元素或浮動方向沒有任何關係,而取決於其前面宣告的浮動元素的浮動方向。
舉個例子,一個右浮動元素(float:right),前面有一個左浮動元素(float:left),如果這個右浮動元素使用clear: left時,這個元素會清除前一個元素的浮動,進而換行顯示;如果使用clear:left時,這個元素在當前行的最右端顯示。如下圖所示(06:layout/clear/4.右浮動清除左浮動.html):
圖1. 右浮動清除左浮動
圖2.右浮動元素清除右浮動
在瞭解完clear屬性的取值和應用場景,我們可以對其作用,可以總結為:
如果當前元素浮動元素或非浮動的塊級元素,且前面宣告的元素是左(右)浮動元素,那麼當前元素可以使用clear: left(clear: right),清除前一個左(右)浮動元素的左(右)浮動,此時當前元素會換行顯示;如果當前元素clear的浮動與前面一個浮動元素的浮動方向不同向,當前元素不會換行;
5.6.3.3. 清除浮動後的margin合併問題
1)兩個浮動元素之間,其垂直方向上的margin不會發生合併,如下圖所示:
浮動元素間會不發生垂直margin合併
2)非浮動的塊級元素和浮動元素之間,其垂直方向上的margin會發生合併,如下圖所示:
非浮動的塊級元素與浮動元素間會發生margin合併
特別地,MDN的文件說非浮動的塊級元素與浮動元素間不會發生margin合併,實際上會,上述結果已經證明,已在MDN上更正該錯誤。以下為MDN未修改前的原話:
MDN未修正前的原話
5.6.3.4. 清除浮動的特殊應用:解決父元素高度塌陷問題
眾所周知,當一個父元素裡面的所有元素都是浮動元素時,此時父元素無法識別這些浮動子元素,會進一步導致父元素髮生高度塌陷問題。一種通用的解決方案就是在父元素內部的尾部append一個非浮動的、尺寸為0的塊級元素(後面簡稱fix元素),然後使用clear:both,讓這個fix元素換行顯示,進而讓父元素能夠識別前一行的高度。這種樸素的方案其實就是clearfix的基本原理,clearfix只是更加優雅地用:after來實現fix元素。
特別說明:解決父元素高度塌陷問題,還可以通過將父元素宣告為BFC元素來實現。
5.6.4. 定位(position屬性)
5.6.4.1. 定位元素的分類
根據position屬性的取值,static(預設值)、relative、absolute、fixed,元素可以分為靜態定位元素(值為static)、相對定位元素(值為relative)、絕對定位元素(值為absoute)和固定定位元素(值為fixed)。
注:position的取值還有sticky,但IE11都不支援,此處不講
5.6.4.2. 定位原理
static定位元素定位時的相對座標系:無法設定top、right、bottom和left這四個偏移屬性;
relative定位元素定位時的相對座標系:元素在文件流原來的位置(區域);
absolute定位元素定位時的相對座標系:離元素最近的一個非static(包含relative、absolute和fixed)定位祖先元素(包含塊為其padding box),如果沒有則為ICB(初始包含塊),即根元素html的包含塊;
fixed定位元素定位時的相對座標系:當前的視窗(viewport);
5.6.5. line box(行框)
5.6.5.1. 定義
前面在介紹IFC時,我們提到過line box的定義:包含IFC內部的所有子元素的虛擬矩形區域,形成的每一行,稱為line box。由於它是矩形的,中文常見將之翻譯為行框。
5.6.5.2. 模型結構(七線譜)
line box的模型結構,形如七線譜,其中有六條重要的線:top線、text-top線、middle線、baseline線、text-bottom線和bottom線,如下圖所示:
行框七線譜
其中top線到text-top線的區域和bottom線到text-bottom的區域,又稱為行半距(half-leading),兩個行半距之和,為一個行距;text-top線到text-bottom線的區域,稱之為內容區域(content-area)。如下圖所示:
行框區域劃分
5.6.5.3. 行框高度的計算
行框的高度,即一行的top線和bottom線間的垂直距離,這個垂直距離為:上下兩個行半距的高度和一個內容區域的高度之和。影響行框高度計算的因素來自兩方面,一是自身line-height屬性的設定,二是內部inline-level子元素的margin box高度的取值和line-height、vertical-align兩個屬性的設定。關於其計算規則,具體羅列如下:
1. 一個元素的行框高度,可由該元素的line-height屬性設定;
2. 一個元素的行框高度,受不可置換(span、a、label等)的子元素的內容高度(text-top到text-bottom的垂直距離)影響(內容高度又受font-size屬性和瀏覽器的解析規則影響,但主要由font-size決定;相同的font-size,在不同的瀏覽器,計算出來的內容高度也不一樣,最終導致的行框高度也不一樣);
3. 一個元素的行框高度,可由不可置換(span、a、label等)的子元素的line-height屬性設定;
4. 一個元素的行框高度,可由可置換行內元素(如img)或display屬性為inline-block、inline-table的這一類inline-block-level子元素的margin box高度和vertical-align屬性決定,當vertical-align為top或bottom時,行框的高度達到最小,剛好為子元素的margin box高度;
圖1. img元素的margin box高度比行框高度小
圖2. img元素的margin box高度與行框高度一致,行框高度達到最小
5. 如果同時滿足以上設定條件,那麼行框的高度取最大值;
友情提示:在圖1 img元素的margin box高度比行框高度小,我們會看到img元素到父元素的底端會有一段空白,為什麼會有這種現象?張鑫旭老師在《CSS深入理解vertical-align和line-height的基友關係》一文中將之定義為“幽靈空白節點”,其實結合行框理論來解釋,這段空白並不“幽靈”,也很好理解:它是行框的baseline線到bottom線的垂直距離,可置行內換元素如img和inline-block-level元素,在被瀏覽器解析時,會和“文字”一樣,預設在baseline線上顯示,而不是在行框的bottom線上。
舉個例子: 行框高度的計算
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<style> .line-box { background: yellow; line-height: 32px; font-size: 20px; } .span-1 { line-height: 40px; background: red; } .span-2 { line-height: 38px; background: green; } img { width: 50px; height: 50px; } </style> <body> <div class="line-box"> <span class="span-1">span(line-height: 40px)</span> <span class="span-2">span(line-height: 38px)</span> </div> <div class="line-box"> <span class="span-1">span(line-height: 40px)</span> <span class="span-2">span(line-height: 38px)</span> <img src="#" /> </div> </body> |
顯示結果(chrome下):
圖1. line box內部僅有不可置換元素
圖2. line box內部還有可置換元素img
說明:
a. 元素每一行的line-height,既可以由當前元素的line-height屬性設定(32px),也可以由該行子元素的line-height屬性設定(分別是40px和38px),但取最大的line-height(40px),如圖1所示;
b. 特別地,如果一行內還有可以設定height的可置換元素如img(height: 50px),且img的高度大於設定的最大line-height(40px)時,那麼該行會被撐高,瀏覽器會重新計算line-height(最終結果為63px),如圖2所示;
5.6.5.4. 與line box行框有關的兩個重要屬性:line-height和vertical-align
相信很多前端同學有這樣的感覺:line-height和vertical-align這兩個屬性總是形影不離,而且有著一種說不清的關係。
它們到底有什麼聯絡嗎?
其實這兩個屬性的關係可由行框和行框內的inline-level元素來體現。line-height屬性決定inline-level元素所在行框的高度,它是inline-level元素在一行內垂直方向上的顯示範圍;vertical-align屬性則決定inline-level元素在一行內的垂直對齊方式,即決定inline-level元素在一行內垂直方向上的最終位置。下面我們來深入介紹這兩個屬性:
1)line-height屬性
1.1)line-height屬性的作用
line-height屬性一般用於塊級元素設定其內部每一行的高度,即預設行高;line-height屬性也可以用於不可置換元素(如span、a)設定所在行框的高度。也就說,每一行計算出來的最終行高,既受父元素line-height屬性的影響,也受子元素line-height屬性的影響。
1.2)line-height屬性的取值
line-height的取值有<length>、<number>、和關鍵字normal(預設值)。其中:
- <length>表示使用指定帶單位的長度來設定line-height,這些長度單位可以是px、pt和em和rem;
- <number>表示用font-size值的倍數來設定line-height;
- <percentage>表示用font-size值的百分比來設定line-height;
- 而關鍵字normal,其最終計算出來的尺寸,則取決於瀏覽器各自的解析機制和選用的font-family型別:瀏覽器會根據選用的font-family型別來計算出一個合適的值,W3C官方推薦使用<number>值,並且推薦值的範圍為1.0到1.2之間(但經過實測,瀏覽器在實現時,遠比這個複雜,而且不同瀏覽器間也存在差異。唯一可以確定的一點是,最終的行高肯定會比font-size值要大)。
我們在將UI稿實現為頁面程式碼時,常常強調要Pixel Perfect、高精準地還原設計稿。但
我們常常會遇到這樣一個問題:當我們用一個塊級元素包裹文字時,會發現塊級元素的高度,實際比文字的font-size尺寸還要高,導致上下形成了一些空白,進一步造成塊級元素內的文字與垂直方向上相鄰元素的距離變大,如下圖所示:
line-height值為normal
這種誤差是由於line-height的預設值為normal,那有什麼辦法可以解決這個問題呢?較常用的方法是將塊級元素的line-height設定為1或100%。設定後的結果如下圖所示:
line-height值為1或100%
這樣做也有一點不好,那就是:瀏覽器最終解析出來的內容高度,有可能是比font-size要大的,當行高為font-size時,文字內容就會溢位。我們將字型放大為100px,溢位效果就很明顯,如下圖所示:
line-height值為1或100%
1.2)line-height屬性對元素高度的影響
我們可以通過了解line-height屬性分別對塊元素和不可置換的行內元素自身高度的影響、以及不可置換的子元素的line-height屬性對父元素高度的影響,來深入理解line-height屬性的作用。
為了幫助大家更好地理解line-height,我設計瞭如下三個小demo:
demo1: line-height屬性對塊級元素自身height的影響
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
</body> // div為單行 <div class="block"> div(line-height: 32px) </div> // div為多行 <div class="block"> div(line-height: 32px) <br> div(line-height: 32px) </div> </body> |
顯示結果(chrome下):
圖1. div為單行時
圖2. div為多行時
說明:
a. 當一個塊元素不設定height,而且這個塊元素僅有一行時,那麼其height剛好等於line-height;
b. 當一個塊元素不設定height,而且這個塊元素有多行時,那麼其height剛好等於每一行的line-height之和;
demo2: line-height屬性對不可置換行內元素(如span)的height的影響
html:
1 2 3 4 5 6 7 8 9 |
<div class="line-box"> <span class="inline-element"> span(line-height: 40px;font-size: 20px) </span> </div> |
顯示結果:
圖1. 行內元素為單行時,height為28px(在chrome下)
圖2. 行內元素單行時,height為20.5px(在firefox下)
說明:
a. 不可置換行內元素為單行時,其height等於text-top線到text-bottom線的距離,所以line-height的取值不會影響到其height,其height由font-size和瀏覽器的預設解析機制決定(一般>font-size,大多少則取決於瀏覽器解析機制,如圖1、2所示);
b. 不可置換元素為多行時,其height等於第一行的text-top線到最後一行的text-bottom線的距離,此時line-height的取值就會影響到其height,其height=line-height * 行數 – (line-height – 每一行text-top線到text-bottom的距離),即height=line-height * 行數 – 2 * half-heading;如下圖所示:
圖3. 行內元素為多行時(在chrome下)
demo3:不可置換的子元素(如span)的line-height,對父元素height的影響
html:
1 2 3 4 5 6 7 8 9 10 11 |
<div class="line-box"> <span class="span-1">span(line-height: 40px)</span> <span class="span-2">span(line-height: 38px)</span> <br> <span class="span-3">span(line-height: 50px)</span> </div> |
顯示結果(chrome下):
說明:
a. 塊級元素每一行的行高都可以不同;
b. 不可置換的行內子元素的line-height屬性,可以決定所在行框的高度;
c. 如果一個父元素不設定height,那麼其height為所有行的高度之和;
d. 不可置換的行內子元素的line-height屬性,是通過影響行框的高度來影響父元素的高度的。
2)vertical-align屬性
vertical-align的作用之一:就是用於設定inline-level元素自身在“行框”內的垂直對齊方式,其控制範圍在一行內。較常用的值有top、middle、baseline(預設值)和bottom,不常用的有text-top、text-bottom、sub和super,幾乎不用的有<length>和<percentage>。
行框七線譜
vertical-align屬性的幾個重要取值的作用如下:
- 當vertical-align取top時,表示當前inline-level元素的上margin edge在行框內貼頂;
- 當vertical-align取bottom時,表示當前inline-level元素的下margin edge在行框內貼底;
- 當vertical-align取middle時,表示當前inline-level元素的垂直平分線和行框的middle線重合;
- 當vertical-align取baseline時,表示當前inline-level元素的下margin edge緊貼在行框的baseline上;
vertical-align屬性的另一個作用:就是table-cell元素用於控制其內部子元素在垂直方向上的對齊方式,而且這些子元素的型別不受限制,不僅可以是span,而且可以是div。
舉個例子:
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<table> <tr> <td class="top">div(top)</td> <td class="middle">div(middle)</td> <td class="baseline">div(baseline)</td> <td class="bottom">div(bottom)</td> </tr> </table> |
顯示結果:
說明:
a. table-cell元素通過設定自身的vertical-align屬性,來設定其子元素在垂直方向上的對齊方式;
特別說明:我們常用說的使用table佈局來實現子元素在父元素內部垂直居中,就是運用到了這個知識點。
5.6.6. margin
在傳統的佈局方案中,margin不僅用來隔離自身與相鄰元素或父元素(一般不推薦用來隔離父元素),而且在元素水平和垂直方向上的居中定位,亦發揮了重要的作用。下面我們來深入介紹margin的相關佈局特性。
5.6.6.1. auto的計算規則(在width和margin上使用)
1)水平方向上
談到“如何設定文件流中的塊級元素在父元素內部水平居中?”這個佈局問題,相信很多同學馬上會想到這個方案:給元素設定固定寬度,並使用margin: 0 auto(水平方向上的margin為auto)
.child {
width: 100px;
margin: 0 auto;
}
它的實現原理,包含如下四個基礎知識點:
- 塊級元素的水平尺寸(outerWidth,margin box的寬度)的計算規則:
outerWidth = margin-left + border-left-width + padding-left + width + padding-right + border-right + margin-right,如下圖所示:
- 文件流中的塊級元素,其在水平方向上的尺寸屬性的初始值,僅width為auto,其餘為0
- 在水平方向上的尺寸屬性,僅width、margin-left和margin-right可以設定auto值(自動計算)
- 文件流中的塊級元素,其在水平方向上的尺寸屬性,當值為auto時,表示取所在行的剩餘寬度,特別地,當margin-left和margin-right的值均為auto時,會平分所在行的剩餘寬度
在理解了上述四個基礎知識點,我們不難理解其原理:
當塊級元素在水平方向上的尺寸屬性,除了margin-left和margin-right值為auto,其餘皆為定值,那麼margin-left和margin-right會自動平分父元素的剩餘寬度,進而達到在父元素內部水平居中的效果,如下圖所示:
圖1. 元素在父元素內部水平居中(左右margin各取一半)
圖2. 水平居中元素的盒模型解構圖
結合上述四個基礎知識點,我們還可以得出如下結論:
文件流中的塊級元素如果不設定任何水平尺寸屬性,那麼其預設的width為當前行的content width,此時width取auto和100%,最終的計算值一樣
2)垂直方向上
或許我們都曾問過這樣的一個問題:既然可以通過設定margin: 0 auto,讓文件流中的塊級元素在父元素內部水平居中,那麼可否通過設定margin: auto 0,讓其垂直居中?
答案是不能的,因為文件流中的塊級元素,其垂直方向上的margin為auto時的計算規則和在水平方向上的計算規則不同:不取父元素剩餘的高度,而為0。W3C標準原話如下:
“If “margin-top” or “margin-bottom” is “auto”, their used value is 0″
或許大家會問,為什麼要這樣設計呢?官方並沒有給出說明,但是有網友給出瞭如下幾個解釋,羅列如下,供各位參考(可以在留言中分享你的看法,本人比較認同第一條):
It could be because of the typical vertical page flow, where page size increases height-wise. So, centering an element vertically in its container is not going to make it appear centered, relative to the page itself, unlike when it’s done horizontally (in most cases).
And maybe it’s because of this same reason, they decided to add an exception for absolute elements which can be centered vertically along the entire page’s height.
It could also be because of the margin collapse effect (a collapse of adjacent elements” margins) which is another exception for the vertical margins.
在W3C標準規約中,雖不能使用margin: auto 0,實現普通文件流中的塊級元素在父元素內部垂直居中,但是可以使用margin: auto 0,實現絕對或固定定位元素在包含塊內部垂直居中,因為絕對或固定定位元素垂直方向上的margin,其 auto仍會取包含塊的剩餘高度,W3C官方文件給出的計算公式如下:
‘top’ + ‘margin-top’ + ‘border-top-width’ + ‘padding-top’ + ‘height’ + ‘padding-bottom’ + ‘border-bottom-width’ + ‘margin-bottom’ + ‘bottom’ = height of containing block
等價的簡化公式:
子元素outerHeight = 包含塊height – 子元素top – 子元素bottom
提示:
1. 子元素outerHeight,是指當前子元素margin box的高度;
2. 包含塊height,可以為當前子元素的相對定位參考系元素的padding box的高度、ICB的高度或viewport的高度;
要使用上述規則來實現子元素在父元素內部垂直居中,那麼就需要保證:
- 子元素的top值 + bottom值為0(原因:讓子元素outerHeight 等於包含塊height)
- 子元素的top值取0(原因:讓子元素的上margin edge緊貼包含塊的頂部)
下面通過一個demo來詳細介紹:
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<style> .parent { position: relative; background: yellow; height: 100px; } .child { position: absolute; top: 0; bottom: 0; background: green; width: 140px; height: 20px; margin: auto 0; text-align: center; } </style> <body> <div class="parent"> <div class="child">垂直居中的子元素</div> </div> </body> |
顯示結果:
圖1. 子元素在父元素內部垂直居中(上下margin各取一半)
圖2. 垂直居中元素的盒模型解構圖
說明:
a. 絕對定位的子元素的top為0,其輪廓(包含margin)的上邊界與其包含塊內容區域的頂部緊貼;
b. 由已知求未知:包含塊的height已知,子元素的top值和bottom值之和為0,即子元素的outerHeight可求,又因為子元素height已知,故垂直方向上的剩餘高度可以確定,當子元素的margin-top和margin-bottom均為auto時,將平分剩餘的高度;
5.6.6.2. margin合併(margin collapsing)
在垂直方向上,元素與自身或相鄰的兄弟元素、父元素、子元素的margin,會發生合併(注意:在IE6/7子元素垂直方向上的margin會隔離父元素,而不是和父元素的margin發生合併,IE8+則與標準瀏覽器同),margin取較大的值,而在水平方向上則不會。各位讀者可以從下面三個demo,來理解垂直方向上margin的合併:
1) 父元素與子元素(第一個子元素、最後一個子元素)
html:
1 2 3 4 5 6 7 8 9 10 11 |
<div class="wrapper"> <div class="parent"> <div class="child first-child">第一個子元素(margin-top: 20px)</div> <div class="child last-child">最後一個子元素(margin-bottom: 20px)</div> </div> </div> |
顯示結果:
說明:
a. 父元素(黃色)的margin-top(40px)和第一個子元素的margin-top(20px)發生融合(取較大的40px);
b. 父元素(黃色)的margin-bottom(40px)和最後一個子元素的margin-bottom(20px)發生融合(取較大的40px);
2) 上下相鄰的兄弟元素(同層元素)
html:
1 2 3 4 5 6 7 |
<div class="parent"> <div class="child first-child">第一個元素(margin-bottom: 40px)</div> <div class="child last-child">第二個元素(margin-top: 20px)</div> </div> |
顯示結果:
說明:
a. 第一個元素的margin-bottom(40px)和第二個元素的margin-top(20px)發生融合(取較大的40px);
3) 空塊級元素
html:
1 2 3 4 5 |
<div class="line">第一行</div> <div class="empty-block"></div> <div class="line">第二行</div> |
顯示結果:
說明:
a. 兩行之間的空白區域,為一個空塊元素;
b. 空塊的margin-top為40px, margin-bottom為20px;
c. 兩行之間的距離為40px,可知空塊元素的margin-top和margin-bottom發生了合併,取較大值;
這裡我們舉了三個會在垂直方向上發生margin合併的例子,但是細心的同學可能記得,我們在“5.6.3.3. 清除浮動後的margin合併問題”章節,舉了一個在垂直方向上例子不會發生margin合併的例子:浮動元素間在垂直方向上不會發生margin合併。
5.6.6.3. 子元素的margin隔離父元素
細心的讀者不難發現,在“2) 上下相鄰的兄弟元素(同層元素)” 的demo可以看到
子元素(綠色)垂直方向上的margin並沒有將自己與父元素(黃色)隔離開(IE6/7會,IE8+不會)。
那麼什麼情況,子元素的margin可以和父元素隔離開?
首先要強調的一點是, 子元素水平方向上的margin,始終能夠隔離父元素;然而子元素在垂直方向上的margin隔離父元素的情況,本人記錄的僅有以下四種(歡迎補充):
case 1: 父元素是BFC元素
html:
1 2 3 4 5 |
<div class="parent"> <div class="child">子元素(margin: 20px)</div> </div> |
顯示結果:
說明:
a. 父元素(黃色)是BFC元素,子元素(綠色)垂直方向上的margin能夠隔離父元素;
case 2:父元素擁有border
html:
1 2 3 4 5 |
<div class="parent"> <div class="child">子元素(margin: 20px)</div> </div> |
顯示結果:
說明:
a. 父元素(黃色)擁有border,子元素(綠色)垂直方向上的margin能夠隔離父元素;
case 3:父元素擁有padding
html:
1 2 3 4 5 |
<div class="parent"> <div class="child">子元素(margin: 20px)</div> </div> |
顯示結果:
說明:
a. 父元素(黃色)擁有padding,子元素(綠色)垂直方向上的margin能夠隔離父元素;
case 4:子元素是可置換元素或display為inline-block、inline-table、table-caption的元素
html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<style> .parent { background: yellow; width: 100%; height: 60px; line-height: 20px; } .inline-block { display: inline-block; } .inline-table { display: inline-table; } img, .inline-block, .inline-table { border: 1px solid green; height: 20px; min-width: 20px; margin-top: 10px; vertical-align: top; } </style> <body> <div class="parent"> <img src="frame_image.svg" /> <div class="inline-block">display: inline-block </div> <div class="inline-table">display: inline-table</div> </div> </body> |
顯示結果:
說明:
a. 可置換行內的和display屬性為inline-block、inline-table的子元素,其垂直方向上的margin能夠隔離自身與父元素;
在這裡對margin合併和margin隔離作一個小結,本人把遇到過的所有在垂直方向上會發生與不會發生margin合併、能使用margin隔離與不能使用margin隔離的例子,都羅列了出來(然而這僅僅是在標準瀏覽器的例子,在IE6/7情況還會不一樣,但因為現在基本無需再相容低版本的IE,所以就不再列舉)。目的不是讓大家記住它,而是讓大家避開它:在垂直方向上,兄弟元素間儘量不要設定相鄰的margin,子元素也不要使用margin來隔離父元素,這樣能儘量保證你的CSS程式碼,在各種版本的瀏覽器都有較好的相容性(顯示一致)。
7. 結尾語
本文從CSS盒模型及其發展史、元素的分類及其佈局特性、格式化上下文(Formatting Context)、包含塊、基本原理(文件流、浮動、清除浮動、定位、行框、margin)這五大模組,系統介紹了一下前端的佈局基礎,希望此次分享,能夠讓各位讀者對前端基礎佈局有一個底層、體系的認識。因為內容涵蓋過廣,難免會有紕漏,還望見諒和指正。
此篇文章斷斷續續寫了幾個月,從年前寫到年後,一方面是因為這個標題太大,含括的內容太多,需要慢慢梳理;一方面是文中要講的東西,很多是出於本人的感悟和總結,為了保證觀點的正確性、嚴謹性以及和行業的標準術語做好同步,需逐一驗證;還有一方面也是近幾個月來,本人需要處理的私事較多,分散了精力。
時隔半年,依然有不少朋友還在關注我的公眾號,謝謝你們的支援。這遲來的一篇分享,希望能對各位有用,後面我也會努力分享更多對大家有幫助的文章。
最後我還想再分享一些心得體會:
- 不要輕視簡單的東西我們在生活中總是容易忽略一些簡單的東西,因為輕視簡單,導致過了幾年依然也沒有掌握,前端的同學更應該注意這一點。
- 盡信書不如無書不要太相信權威,而是要學會驗證、總結,並構建自己的知識體系。
- 學技術要看官方文件很多同學在初學時喜歡看一些快速入門的教程,我覺得這種學習習慣挺好的,但是建議不要遺漏官方文件的學習。因為初學者很難去鑑定一個非官方文件的質量,運氣不好的話,還會被誤導。而且官方文件最貼近原作者的想法,我們更容易體會到其設計思想。
演示程式碼地址:https://github.com/momopig/simplicity/tree/master/06:layout