CSS 中不為人知的部分

如果你在日常工作中使用 CSS,你的主要目標可能會重點圍繞著使事情"看起來正確"。如何實現這一點經常是遠不如最終結果那麼重要。更多互聯網訊息請在

公眾號鎖定“全階魔方”每天都有更新。這意味著比起正確的語法和視覺結果來說,我們更少關心 CSS 的工作原理。

CSS 的視覺結果通常是操作隱藏屬性的間接後果,你可能還沒有意識到這一點。某些 CSS 屬性(比如 background-color)與你看到的內容有直接而明顯的關係。同時,其它像 display 這樣的屬性對於我們很多人來說仍然是模稜兩可的,因為結果似乎與上下文環境有很大關係。

我懷疑很多開發人員都不能用簡單的術語描述設置 display: block 實際上是在做什麼,頂多可能對這種屬性如何工作有一些直觀的瞭解。沒關係,你可以在不瞭解底層原理的情況下,就能 CSS 瞎嚷嚷。不過,那也是隻知其一,不知其二。

如果這就是在說你,沒關係。我也是在學會如何使用 CSS 很久以後,才理解 CSS 的工作原理。我猜這並不能讓你好受點,不過至少你並不是一個人在戰鬥!

CSS 的底層特性是複雜而有意抽象的,但我們也不能完全不瞭解它們。對於我們很多人來說,或多或少都會熟悉像盒模型、層疊和特殊性這些概念。雖然它們經常被曲解,但是懂點這些概念的工作原理有助於我們編寫更好的 CSS。

對於 CSS 的很多其它隱藏部分也可以這樣說。問題是更好地理解這些部分的門檻更高。經常會感覺好像沒有什麼能孤立地解釋。需要先了解所有內容,然後才能理解流程的最小部分。

因此,我想嘗試揭示一些 CSS 不為人知的部分,僅觸及你需要知道的內容,並希望以邏輯順序解釋該過程,以便更好地理解 CSS 的實際工作原理。

這是一篇很長的文章,所以如果你想跳過這些部分之一,我完全可以理解。

渲染過程概述

當加載一個 HTML 文檔時,為了讓該頁面渲染,有很多事情要發生。

第一個步驟就是解析 HTML 文檔。瀏覽器從這一步創建一個文檔樹。樹狀結構是表示像 HTML 這種具有明顯層次結構信息的一種方法。樹中的元素可以被描述為類似於族譜,比如祖先、父親、孩子和兄弟姐妹。

你可能聽說過術語 DOM。它代表文檔對象模型(Document Object Model)。這是文檔樹結構的一種擴展,被用於存儲和操作 Web 文檔內容有關的信息。

隨著 HTML 被解析,樣式表和其它資源也被獲取。樣式聲明通過一個稱為層疊的過程來解釋和確定。

在此過程期間,CSS 屬性的最終值被確定。在計算後,這些值可能與樣式表中定義的有所不同。例如,像 auto 這種關鍵字和相對單位被指派了真實值,並且繼承值被應用了。這些計算值被存儲在一個樹中,類似於 DOM 中的元素,毫不奇怪,它被稱為 CSS 對象模型或者 CSSOM。

現在才有可能開始渲染頁面的過程。這個過程的第一步是盒模型的計算。這是一個算出元素的尺寸和間距的重要步驟,雖然不一定是元素的最終位置。

與盒模型相比,不那麼被人熟知的是一個稱為視覺格式化模型的過程。這個過程確定元素在頁面上的佈局和位置。它包含了一些你可能已經熟悉的概念,比如定位方案、格式化上下文、顯示模式和堆疊上下文。

最後,頁面被渲染。

上面的段落中可能有一些你還不熟悉的一些術語。如果是這樣的話,最重要的是要理解層疊、盒模型以及視覺格式化模型。這些術語都是解釋、處理和渲染 HTML 和 CSS 的核心步驟。在描述這些術語時,我跳過很多細節,所以我們下面要更詳細地研究一下這三個步驟。

層疊

層疊可能是最被誤解的 CSS 特性之一。它是指組合不同樣式表以及解決 CSS 選擇器之間衝突的過程。

層疊查看聲明的重要性、來源、特殊性以及順序,來確定使用哪個樣式規則。

你需要知道什麼:

大多數網站有多個樣式表。通常,樣式是用引用一個 CSS 文件的一個 link 標記,或者 HTML 主體部分的 style 標記添加的。即使最基礎的頁面也會有瀏覽器提供的默認樣式。這種默認樣式表有時被稱為 user-agent 樣式表。

在層疊期間,樣式表是按如下順序被解釋的:

2. 作者的樣式表

3. 瀏覽器默認的樣式表

注意:這裡我跳過了用戶樣式表,是因為它們不再常見,可能不會是讀這本文的所有人要考慮的因素。

在組合這些來源後,如果多個規則應用到同一元素,就用特殊性來確定應用哪條規則。

特殊性

特殊性(Specificity)是給選擇器的權重。它常被誤認為是一個數字。實際上是 4 個單獨的數字或者 4 種權重類別。

要計算特殊性,要統計如下選擇器的數目:

1. ID 選擇器

2. 類選擇器、屬性選擇器和偽類選擇器

3. 元素選擇器和偽元素選擇器

例如:#nav .selected:hover > a::before 將是 1, 2, 2。

無論有多少個類選擇器,都不會比一個 ID 選擇器有更高的特殊性。當比較選擇器時,我們首先比較 ID 選擇器的特殊性。只有 ID 選擇器相等時,才比較類選擇器、屬性選擇器和偽類選擇器的特殊性值。如果最後依然相等,才比較元素和偽元素選擇器。

如果每個類別的特殊性都是相等的,那麼來源中最後的聲明獲勝。

是的!我知道我說的是 4 個類。行內樣式比 ID 選擇器有更高的特殊性。不過由於在技術上行內樣式在特殊性計算中是第一類,通常你不會與行內樣式競爭,所以很容易記住行內樣式的特殊性總是會佔優。

重要的注意事項:雖然 !important 聲明不會作為特殊性計算的因素,不過它們比層疊中的普通聲明有更大的優先級。

繼承

繼承不是層疊的一部分,不過我在這裡把它包含進來,是因為它經常被與層疊結合在一起討論。

繼承是應用給一個元素的值可以被傳遞到(或者繼承)子元素上的過程。

你肯定知道這個事實,即當把字體屬性應用到 body 或者其它容器元素時,該屬性也會被容器內的所有元素所繼承。這就是繼承。

並非所有屬性默認都被繼承。理解繼承是編寫更優雅更簡潔 CSS 的關鍵。有時候用 inherit 關鍵字強制繼承會相當有用。

注意: 有些屬性(比如 border-color)默認值是 currentcolor。也就是說,它們會使用在 color 屬性上設置的值。默認值與繼承不是一碼事。不過,color 屬性本身經常是被繼承的,所以我傾向於認為這是一種事實上的繼承。

盒模型

理解盒模型是佈局和定位時防止挫敗所必需的。盒模型是 CSS 中最基本的概念之一。

盒模型用於計算元素的寬度和高度。它只是計算過程中的一個步驟,確定元素的最終佈局和定位並非完全依賴於它。

你需要知道什麼:

HTML 中的每個元素都是一個矩形的盒子。每個盒子有用元素的外邊距(margin)、邊框(border)、內邊距(padding)和內容區定義的四個區域。

默認情況下,當我們設置一個元素的寬度時,只是設置內容區的寬度。當給一個元素添加內邊距、邊框或者外邊距時,是增加了除寬度以外的部分。實際上,這就是說寬度為 50% 的兩個元素如果添加了內邊距、外邊距或者邊框,就不會並排填滿寬度(因為已經超過了 100% 寬度)。

CSS 中不為人知的部分

就是這樣!相當簡單對吧?那麼為什麼這經常是困惑的來源呢?好吧,你可能已經遇到過一些事情好像表現得有點不同的情況。。。

填充區

當設置元素的背景時,不僅會填充內容區,還會填充內邊距區和邊框區。

CSS 中不為人知的部分

概念上,我們把一個 HTML 元素當作是一個東西,所以很容易認為一個元素的視覺邊界等於它的寬度,不過實際卻並非如此。雖然一個元素的視覺邊界包含了內邊距和邊框區,不過 width 屬性是顯式地被應用到內容盒。

注意: 更改 box-sizing 屬性可以改變這種行為。

Width Auto

另一個潛在的困惑來源是 width: auto 的工作機制。寬度為 auto 是大多數 HTML 元素的默認元素,對於像 div、p 這種塊元素來說,auto會計算寬度,這樣外邊距、邊框、內邊距以及內容區都組合在一起也能剛好放在可用空間內。

在這種情況下,感覺添加內邊距和外邊距會向內擠壓內容,不過實際上,寬度會被重新計算,以確保所有東西都能剛好放下。相比之下,在設置寬度為 100% 時,光內容區就會把可用空間填滿,而不管外邊距、內邊距和邊框。

CSS 中不為人知的部分

Box-sizing

box-sizing 屬性會改變盒模型的工作方式。當 box-sizing 被設置為 border-box 時,內邊距和邊框會減少內容區的內部寬度,而不是添加元素的整體寬度。也就是說,元素的寬度現在與其視覺寬度是相同的。

CSS 中不為人知的部分

很多人喜歡這個屬性,而且如果你正在創建柵格系統,或者其它需要水平對齊條目的任何佈局類型,這個屬性就是更直觀的工作方式。

外邊距合併

當外邊距意外合併,而你不知道發生了什麼時,真的很令人困惑。當兩到多個垂直相鄰的外邊距相接觸,並且沒有用內邊距或者邊框分隔開時,外邊距有時會合並。如果子元素的外邊距伸進其父元素的外邊距,並且沒有被內邊距分隔開時,外邊距合併也會發生。

如果元素是絕對定位、浮動或者有不同的格式化上下文以及在其它一些不可能的情況下時,外邊距不會合並。

如果你感到困惑,沒有關係。關於外邊距什麼時候會合並,什麼時候不會合並的規則是很複雜的。你需要知道的主要事情是,當元素沒有內邊距或者邊框時,垂直外邊距可以合併。

如果你需要了解得更詳細,CSS Tricks 上有一篇很精彩的。

視覺格式化模型

盒模型計算元素的大小,而視覺格式化模型(Visual Formatting Model)負責確定這些盒子的佈局。視覺可視化模型考慮盒子的類型、定位方案、元素之間的關係以及內容施加的約束,來確定頁面上每個元素的最終位置和呈現。

你需要知道什麼:

視覺格式化模型遍歷文檔樹,根據 CSS 盒模型生成一到多個渲染元素所需的盒子。CSS 的 display 屬性在確定一個元素如何參與當前上下文和定位方案中發揮關鍵作用。這些部分綜合在一起,確定元素的最終佈局和定位。

這是一個複雜的步驟,也是目前為止最難嘗試和總結的。如果你還沒了解所有這些知識的話,沒有關係。理解如何通過 CSS 屬性操縱定位方案和格式化上下文是個好的開端。如果你理解了該模型的不同部分之間的相互作用的話,你就會比大多數人做得更好。起碼你應該知道它們的存在。

Display 類型

我們知道,在 CSS 中設置 display 屬性可以確定如何渲染元素,但是還不清楚其工作原理。事實上,它有時甚至好像是不可預測的。

這是因為,display 屬性確定了元素的"盒子類型"。這個隱藏屬性由一個內部顯示類型和一個外部顯示類型組成,二者在一起幫助確定如何渲染元素。

外部顯示類型通常要麼是解析為 block,要麼是解析為 inline,並且與我們對 CSS 中這些 display 屬性的期望幾乎是一致的。從技術上講,外部顯示類型規定一個元素如何參與其父元素的格式化上下文。

內部顯示類型決定元素會生成什麼樣的格式化上下文。這會影響其子元素的佈局排列方式。

想想彈性盒容器的工作機制。其外部類型是 block,內部類型是 flex。它的子元素也可以有一個 block 外部類型,不過子元素的佈局是受彈性盒容器的格式化上下文所影響的。

思考這種問題的一個方法是,display 的職責是在元素及其父元素之間共享的。

格式化上下文

格式化上下文都是與佈局有關。它們是控制容器內元素的佈局以及它們之間如何相互作用的規則。

有些格式化上下文可以直接在容器上設置,比如通過用 display 屬性值 flex、grid 或 table。其它類型的格式化上下文,比如塊格式化上下文和行內格式化上下文可以被瀏覽器根據需要創建。

注意:在過去,因為塊格式化上下文與浮動交互的方式,理解如果讓瀏覽器建立一個新的塊格式化上下文是很重要的。設置為塊格式化上下文的元素會包含浮動。不過現在它沒有以前那麼重要。事實上,它甚至都不是現代的工作原理了。

定位方案

一個盒子可以根據三種定位方案之一來佈局,包括常規流、浮動和絕對定位。你可能熟悉浮動和絕對定位,因為在編寫 CSS 時,我們會更直接與這兩個打交道。常規流只是一個在元素沒有浮動或者定位時默認定位方案的名字。

常規流

常規流(Normal Flow)描述了默認定位方案,'in-flow'(流內)描述遵從該方案的元素。你可以把流內當作是元素根據其源順序和格式化上下文佈局時的自然位置。

浮動

浮動(float)是一個 CSS 屬性,它會導致元素脫離常規流,儘可能遠地向左或者向右移動,直到它挨著其包含盒或者另一個浮動元素的邊緣。當浮動發生時,文本和行內元素會環繞著浮動元素。

正常情況下,如果沒有設置浮動,一個元素的高度會調整以容納其所有的後代元素。當元素被浮動時,就會從流中脫離出來,這意味著容器不會調整其高度以清除它們。

正是這種行為,讓多行文本、標題以及其它元素環繞著浮動內容而流動。但是有時這是有問題的。清除浮動並設立新的塊格式化上下文會導致容器清除其浮動子元素。這種技術允許浮動被用在佈局上,已經成為 Web 開發技術的奠基石很久了。它依然是要知道的重要技術,不過正在逐漸被像 Flexbox 和 Grid 這種更新的佈局技術所替代。

絕對定位

絕對定位的元素是徹底從流中刪除,並且不像浮動元素,它們對周圍的內容沒有影響。

相對定位的容器允許我們用絕對定位來控制後代元素的偏移。

相對定位元素也可以設置偏移,不過這種偏移是相對於父元素的常規位置,而不是另一個相對定位的容器。

CSS 屬性 top、bottom、left 和 right 被用於計算盒子的偏移。這些屬性都不是二維偏移,不過都可以相對於其容器的內容盒來定位每個邊。

具有重疊偏移的定位元素可以導致元素佔用同一空間。堆疊上下文用於解決這個問題。

堆疊上下文

堆疊上下文決定渲染到頁面上的東西的順序。你可以把一個堆疊上下文當作一個層。堆疊底部的層先繪製,該堆疊之上更高的元素出現在上面。

在一個絕對或者相對定位元素上設置 z-index 屬性是建立新的堆疊上下文的最常見手段。不過,有很多組成堆疊上下文的其它手段,包括設置透明度(opacity)、轉換(transform)、濾鏡(filter)或者使用 will-change 屬性。

不過,使用這些其它手段的原因並不直觀,並且比開發者的預期相比,這些手段對渲染性能有一定影響。只不過它們有助於理解這些層可以被瀏覽器單獨渲染。因此,有時因為性能的原因,有意創建新的堆疊上下文是很有用的。

除非建立了堆疊上下文,否則設置 z-index 是沒有效果的。z-index 越高,層在堆疊中放在的位置越高。

有關堆疊的最讓人感到困惑的部分之一是,一個新的堆疊上下文可以建立在一個已有的堆疊上下文內。也就是說可以有層中層。

在這種情況下,並非總是最高的 z-index 會放在最上面。

就是這樣了!

差不多 3000 個字,我只是大致介紹了 CSS 的重要隱藏部分的一些知識。如果你已經全部讀完了,那麼恭喜你,請一定讓我知道,因為你值得被獎勵!

如果你只是讀了某些部分,也沒關係。希望我已經設法澄清了一些事情,或者對涉及的過程給出了初步的見解。在不犧牲準確性的條件下,用簡單的術語解釋這些東西是一種真正的挑戰。我希望我做對了。


分享到:


相關文章: