級聯、特異性與繼承
本課的目的是幫助你理解 CSS 中一些最基本的概念——級聯、特異性和繼承——它們控制 CSS 如何應用於 HTML 以及如何解決樣式宣告之間的衝突。
雖然學習本課的內容在短期內可能看起來不太相關,而且比課程的其他一些部分更具學術性,但理解這些概念可以讓你避免以後遇到很多麻煩!我們建議你仔細學習本節內容,並在繼續學習之前確保你理解了這些概念。
衝突規則
CSS 代表 **層疊樣式表**,而第一個詞 *層疊* 非常重要——級聯的行為方式是理解 CSS 的關鍵。
在某些時候,你將在專案中發現你認為應該應用於某個元素的 CSS 無法正常工作。通常,問題在於你建立了兩個規則,它們將同一個屬性的不同值應用於同一個元素。 **級聯** 和與其密切相關的 **特異性** 概念是控制在發生衝突時應用哪個規則的機制。正在為你的元素設定樣式的規則可能不是你期望的規則,因此你需要了解這些機制是如何工作的。
同樣重要的是 **繼承** 的概念,這意味著某些 CSS 屬性預設情況下會繼承在當前元素的父元素上設定的值,而有些則不會。這也會導致一些你可能意想不到的行為。
讓我們首先快速瞭解一下我們要處理的關鍵內容,然後我們將依次檢視每個內容,並瞭解它們如何相互作用以及與你的 CSS 如何互動。這些概念可能看起來很難理解。隨著你獲得更多編寫 CSS 的實踐經驗,它的工作原理會變得更加清晰。
級聯
樣式表 **級聯**——從最簡單的層面上來說,這意味著 CSS 規則的來源、級聯層和順序都很重要。當來自同一級聯層的兩條規則都適用並且特異性相同的時候,樣式表中最後定義的那條規則將會被使用。
在下面的示例中,我們有兩個規則可以應用於 <h1> 元素。<h1> 內容最終會變成藍色。這是因為這兩個規則都來自同一個來源,具有相同的元素選擇器,因此具有相同特異性,但是原始碼順序中最後出現的規則會勝出。
特異性
**特異性**(Specificity)是瀏覽器用來決定哪個屬性值應用於元素的演算法。如果多個樣式塊具有不同的選擇器,這些選擇器使用不同的值配置相同的屬性並定位相同的元素,則特異性將決定應用於該元素的屬性值。特異性基本上是衡量選擇器選擇範圍有多具體的指標。
- 元素選擇器特異性較低;它將選擇頁面上出現的所有該型別元素,因此權重較低。偽元素選擇器與普通元素選擇器具有相同特異性。
- 類選擇器特異性較高;它只會選擇頁面上具有特定
class屬性值的元素,因此權重較高。屬性選擇器和偽類與類具有相同的權重。
下面,我們再次有兩個規則可能應用於<h1>元素。下面的<h1>內容最終顯示為紅色,因為類選擇器main-heading使其規則具有更高的特異性。因此,即使使用<h1>元素選擇器的規則在源順序中出現得更靠後,但使用類選擇器定義的特異性更高的規則將被應用。
稍後我們將解釋特異性演算法。
繼承
在這種情況下,還需要了解繼承——父元素上設定的一些 CSS 屬性值會被其子元素繼承,而有些則不會。
例如,如果您在某個元素上設定了color和font-family,則其中的每個元素也將使用該顏色和字型進行樣式設定,除非您已直接向它們應用了不同的顏色和字型值。
某些屬性不會繼承——例如,如果您在某個元素上設定了width為 50%,則其所有後代都不會獲得其父級寬度 50% 的寬度。如果真是這樣,CSS 將非常令人沮喪!
注意:在 MDN CSS 屬性參考頁面上,您可以找到一個名為“正式定義”的技術資訊框,其中列出了有關該屬性的一些資料點,包括它是否被繼承。例如,請參閱color 屬性的正式定義部分。
瞭解這些概念如何協同工作
這三個概念(級聯、特異性和繼承)共同控制哪些 CSS 應用於哪個元素。在下面的部分中,我們將瞭解它們如何協同工作。有時這看起來可能有點複雜,但隨著您對 CSS 越來越熟悉,您會開始記住它們,如果您忘記了,也可以隨時檢視詳細資訊!即使是經驗豐富的開發人員也記不住所有細節。
以下影片展示瞭如何使用 Firefox DevTools 檢查頁面的級聯、特異性等
瞭解繼承
我們將從繼承開始。在下面的示例中,我們有一個<ul>元素,其中嵌套了兩級無序列表。我們為外部<ul>設定了邊框、填充和字型顏色。
color屬性是一個繼承屬性。因此,color屬性值將應用於直接子元素以及間接子元素——直接子元素<li>以及第一個巢狀列表中的子元素。然後,我們將類special新增到第二個巢狀列表中,併為其應用了不同的顏色。然後,它將繼承到其子元素中。
諸如width(如前所述)、margin、padding和border之類的屬性不是繼承屬性。如果邊框要由此列表示例中的子元素繼承,則每個列表和列表項都將獲得一個邊框——這可能不是我們想要的任何效果!
儘管每個 CSS 屬性頁面都列出了該屬性是否被繼承,但如果您知道屬性值將設定哪個方面,通常可以憑直覺猜測出相同的結果。
控制繼承
CSS 提供了五個用於控制繼承的特殊通用屬性值。每個 CSS 屬性都接受這些值。
inherit-
將應用於所選元素的屬性值設定為與其父元素相同。實際上,這“開啟了繼承”。
initial-
將應用於所選元素的屬性值設定為該屬性的初始值。
revert-
將應用於所選元素的屬性值重置為瀏覽器的預設樣式,而不是應用於該屬性的預設值。在許多情況下,此值的作用類似於
unset。 revert-layer-
將應用於所選元素的屬性值重置為先前級聯層中建立的值。
unset-
將屬性重置為其自然值,這意味著如果屬性自然繼承,則其行為類似於
inherit,否則其行為類似於initial。
注意:有關這些內容及其工作原理的更多資訊,請參閱源型別。
我們可以檢視一個連結列表,並探索通用值的工作原理。下面的即時示例允許您使用 CSS 並檢視更改時會發生什麼。玩弄程式碼確實是更好地理解 HTML 和 CSS 的最佳方法。
例如
- 第二個列表項應用了類
my-class-1。這將巢狀在其中的<a>元素的顏色設定為inherit。如果您刪除該規則,它會如何更改連結的顏色? - 您是否理解第三和第四個連結的顏色原因?第三個連結設定為
initial,這意味著它使用屬性的初始值(在本例中為黑色),而不是連結的瀏覽器預設值(藍色)。第四個連結設定為unset,這意味著連結文字使用父元素的顏色,綠色。 - 如果您為
<a>元素定義了一種新顏色(例如a { color: red; }),哪些連結的顏色會更改? - 在閱讀下一節關於重置所有屬性的內容後,返回並將其
color屬性更改為all。請注意第二個連結在新的一行上並帶有一個專案符號。您認為哪些屬性被繼承了?
重置所有屬性值
CSS 簡寫屬性all可用於將這些繼承值中的一個一次應用於(幾乎)所有屬性。其值可以是任何一個繼承值(inherit、initial、revert、revert-layer或unset)。這是一種方便的方法,可以撤消對樣式所做的更改,以便在開始新的更改之前恢復到已知的起點。
在下面的示例中,我們有兩個塊引用。第一個已將樣式應用於塊引用元素本身。第二個已將類應用於塊引用,該類將all的值設定為unset。
嘗試將all的值設定為其他一些可用值,並觀察差異。
瞭解級聯
我們現在瞭解了為什麼巢狀在 HTML 結構深處的段落與應用於 body 的 CSS 顏色相同,這是因為繼承的原因。從入門課程中,我們瞭解瞭如何在文件中的任何位置更改應用於某些內容的 CSS——無論是透過將 CSS 分配給元素還是透過建立類。現在,我們將瞭解級聯如何在多個樣式塊將相同屬性但具有不同值應用於同一元素時定義哪些 CSS 規則適用。
需要考慮三個因素,此處按重要性遞增的順序列出。後面的因素會覆蓋前面的因素
- 源順序
- 特異性
- 重要性
我們將研究這些內容,以瞭解瀏覽器如何準確確定應應用哪些 CSS。
源順序
我們已經瞭解了源順序對級聯的重要性。如果您有多個規則,所有規則都具有完全相同的權重,則 CSS 中最後出現的規則將獲勝。您可以將其視為:越靠近元素本身的規則會覆蓋之前的規則,直到最後一個規則獲勝並能夠為元素設定樣式。
只有當規則的特異性權重相同時,源順序才重要,因此讓我們看看特異性。
特異性
您經常會遇到這種情況:您知道某個規則在樣式表中出現較晚,但會應用較早的衝突規則。發生這種情況是因為較早的規則具有**更高的特異性**——它更具體,因此瀏覽器會將其選為應為元素設定樣式的規則。
正如我們在本課前面看到的,類選擇器比元素選擇器權重更大,因此類樣式塊中定義的屬性將覆蓋元素樣式塊中定義的屬性。
這裡需要注意的是,雖然我們正在考慮選擇器以及應用於其選擇的文字或元件的規則,但並非整個規則都被覆蓋,只有在多個位置宣告的屬性才會被覆蓋。
此行為有助於避免 CSS 中的重複。一種常見的做法是為基本元素定義通用樣式,然後為不同的元素建立類。例如,在下面的樣式表中,我們為 2 級標題定義了通用樣式,然後建立了一些僅更改某些屬性和值的類。最初定義的值將應用於所有標題,然後更具體的值將應用於具有這些類的標題。
現在讓我們看看瀏覽器將如何計算特異性。我們已經知道元素選擇器特異性低,可以被類覆蓋。本質上,會為不同型別的選擇器分配一個分數,將這些分數加起來即可得到該特定選擇器的權重,然後可以將其與其他潛在匹配進行評估。
選擇器具有的特異性使用三個不同的值(或元件)來衡量,可以將其視為 ID、CLASS 和 ELEMENT 列,分別位於百位、十位和個位。
- **識別符號**:對於整體選擇器中包含的每個 ID 選擇器,在此列中得分 1。
- **類**:對於整體選擇器中包含的每個類選擇器、屬性選擇器或偽類,在此列中得分 1。
- **元素**:對於整體選擇器中包含的每個元素選擇器或偽元素,在此列中得分 1。
否定(:not())、關係選擇器(:has())、匹配任何(:is())偽類和CSS 巢狀本身不會增加特異性,但其引數或巢狀規則會增加特異性。每個貢獻給特異性演算法的特異性權重是引數或巢狀規則中權重最大的選擇器的特異性權重。
下表顯示了一些孤立的示例,讓您進入狀態。嘗試瀏覽這些示例,並確保您理解為什麼它們具有我們給出的特異性。我們還沒有詳細介紹選擇器,但您可以在 MDN 的選擇器參考中找到每個選擇器的詳細資訊。
| 選擇器 | 識別符號 | 類 | 元素 | 總特異性 |
|---|---|---|---|---|
h1 |
0 | 0 | 1 | 0-0-1 |
h1 + p::first-letter |
0 | 0 | 3 | 0-0-3 |
li > a[href*="en-US"] > .inline-warning |
0 | 2 | 2 | 0-2-2 |
#identifier |
1 | 0 | 0 | 1-0-0 |
button:not(#mainBtn, .cta) |
1 | 0 | 1 | 1-0-1 |
在我們繼續之前,讓我們看看一個實際示例。
這裡發生了什麼?首先,我們只對本示例的前七條規則感興趣,並且您會注意到,我們在每個規則之前都包含了其特異性值作為註釋。
- 前兩個選擇器正在爭奪連結背景顏色的樣式。第二個選擇器獲勝並使背景顏色變為藍色,因為它在鏈中包含一個額外的 ID 選擇器:其特異性為 2-0-1 對 1-0-1。
- 選擇器 3 和 4 正在爭奪連結文字顏色的樣式。第二個選擇器獲勝並使文字顏色變為白色,因為雖然它少了一個元素選擇器,但缺少的選擇器被一個類選擇器替換,類選擇器比無限個元素選擇器權重更大。獲勝的特異性為 1-1-3 對 1-0-4。
- 選擇器 5-7 正在爭奪連結懸停時的邊框樣式。選擇器 6 顯然輸給了選擇器 5,特異性為 0-2-3 對 0-2-4;它在鏈中少了一個元素選擇器。但是,選擇器 7 擊敗了選擇器 5 和 6,因為它在鏈中與選擇器 5 具有相同數量的子選擇器,但一個元素已被一個類選擇器替換。因此,獲勝的特異性為 0-3-3 對 0-2-3 和 0-2-4。
注意:每種選擇器型別都有其自己的特異性級別,無法被特異性級別較低的選擇器覆蓋。例如,一百萬個類選擇器組合無法覆蓋一個id選擇器的特異性。
評估特異性的最佳方法是從最高開始逐個對特異性級別進行評分,並在必要時轉向最低級別。只有當選擇器分數在特異性列中出現平局時,您才需要評估下一列;否則,您可以忽略特異性較低的選擇器,因為它們永遠無法覆蓋特異性較高的選擇器。
內聯樣式
內聯樣式,即style屬性內的樣式宣告,優先於所有普通樣式,無論特異性如何。此類宣告沒有選擇器,但其特異性可以理解為 1-0-0-0;始終高於任何其他特異性權重,無論選擇器中有多少個 ID。
!important
CSS 中有一個特殊的片段,您可以使用它來覆蓋上述所有計算,甚至包括內聯樣式——!important標記。但是,在使用它時應格外小心。此標記用於使單個屬性和值對成為最具體的規則,從而覆蓋級聯的正常規則,包括正常的內聯樣式。
注意:瞭解!important標記的存在非常有用,這樣您在其他人的程式碼中遇到它時就知道是什麼了。但是,我們強烈建議您除非絕對必要,否則不要使用它。!important標記會更改級聯的正常工作方式,因此它可能使除錯 CSS 問題變得非常困難,尤其是在大型樣式表中。
讓我們看一下此示例,其中有兩個段落,其中一個具有 ID。
讓我們逐步瞭解這裡發生了什麼——如果您發現難以理解,請嘗試刪除某些屬性以檢視會發生什麼。
- 您會看到,第三條規則的
color和padding值已被應用,但background-color尚未應用。為什麼?實際上,所有三個規則都應該應用,因為源順序中較後的規則通常會覆蓋較早的規則。 - 但是,上面的規則獲勝,因為類選擇器比元素選擇器特異性更高。
- 兩個元素都具有
better的class,但第二個元素也具有winning的id。由於 ID 的特異性比類更高(您在一個頁面上只能有一個具有唯一 ID 的元素,但可以有多個具有相同類的元素——ID 選擇器在其目標方面非常具體),因此紅色背景顏色和 1px 黑色邊框都應應用於第二個元素,第一個元素將獲得灰色背景顏色,並且沒有邊框,如類中指定的那樣。 - 第二個元素確實獲得了紅色背景顏色,但沒有邊框。為什麼?因為第二個規則中使用了
!important標記。在border: none之後新增!important標記意味著此宣告將勝過前一個規則中的border值,即使ID選擇器的特異性更高。
注意:覆蓋重要宣告的唯一方法是在原始碼順序中稍後包含另一個具有相同特異性的重要宣告,或具有更高特異性的宣告,或在先前的級聯層中包含重要宣告(我們還沒有介紹級聯層)。
在某些情況下,您可能需要使用!important標記,例如當您在無法編輯核心CSS模組的CMS上工作時,並且您確實想要覆蓋內聯樣式或無法以其他方式覆蓋的重要宣告。但是,如果可以避免,請儘量不要使用它。
CSS 位置的影響
最後,務必注意,CSS宣告的優先順序取決於它在哪個樣式表和級聯層中指定。
使用者可以設定自定義樣式表來覆蓋開發人員的樣式。例如,視力障礙的使用者可能希望將所有訪問網頁的字型大小設定為正常大小的兩倍,以便更容易閱讀。
也可以在級聯層中宣告開發人員樣式:您可以使非分層樣式覆蓋在層中宣告的樣式,或者使在後續層中宣告的樣式覆蓋先前宣告的層的樣式。例如,作為開發人員,您可能無法編輯第三方樣式表,但您可以將外部樣式表匯入到級聯層中,以便您的所有樣式輕鬆覆蓋匯入的樣式,而無需擔心第三方選擇器的特異性。
覆蓋宣告的順序
衝突的宣告將按以下順序應用,後面的宣告將覆蓋前面的宣告
- 使用者代理樣式表中的宣告(例如,瀏覽器預設樣式,在沒有其他樣式設定時使用)。
- 使用者樣式表中的普通宣告(使用者設定的自定義樣式)。
- 作者樣式表中的普通宣告(這些是我們,網頁開發人員設定的樣式)。
- 作者樣式表中的重要宣告。
- 使用者樣式表中的重要宣告。
- 使用者代理樣式表中的重要宣告。
注意:對於使用!important標記的樣式,優先順序順序是相反的。網頁開發人員的樣式表覆蓋使用者樣式表是有意義的,這樣可以保持設計意圖;但是,有時使用者有充分的理由覆蓋網頁開發人員的樣式,如上所述,這可以透過在其規則中使用!important來實現。
級聯層的順序
即使級聯層是一個高階主題,您可能不會立即使用此功能,但瞭解層如何級聯非常重要。
當您在級聯層中宣告CSS時,優先順序順序由宣告層的順序決定。在任何層之外宣告的CSS樣式將組合在一起,按照這些樣式宣告的順序,組合到一個未命名的層中,就像它是最後一個宣告的層一樣。對於競爭的普通(非重要)樣式,後面的層優先於前面定義的層。但是,對於使用!important標記的樣式,順序將反轉,前面層中的重要樣式優先於後續層或任何層之外宣告的重要樣式。內聯樣式優先於所有作者樣式,無論層如何。
當您在不同層中的多個樣式塊為單個元素上的屬性提供衝突的值時,宣告層的順序決定優先順序。層之間的特異性無關緊要,但單個層內的特異性仍然重要。
讓我們從上面的示例中討論一些內容,以瞭解發生了什麼。已按順序聲明瞭兩個層,firstLayer和secondLayer。即使secondLayer中的特異性最高,也不會使用該宣告中的任何屬性。為什麼?因為非分層普通樣式優先於分層普通樣式,無論特異性如何,並且重要分層樣式優先於在後續層中宣告的重要樣式,同樣,無論特異性如何。
如果將此示例中的第一行CSS更改為@layer secondLayer, firstLayer;,您將更改層宣告順序,並且firstLayer中的所有重要樣式將更改為其在secondLayer中的相應值。
作用域鄰近性
另一個您可能不會立即使用但將來可能需要了解的高階主題是@scope。這是一個at規則,使您能夠建立僅適用於頁面上特定HTML子部分的一組規則。例如,您可以指定僅適用於<img>元素的樣式,當它們巢狀在具有feature類的元素內部時。
@scope (.feature) {
img {
border: 2px solid black;
display: block;
}
}
作用域鄰近性是解決作用域元素之間衝突的機制。作用域鄰近性指出,當兩個作用域具有衝突的樣式時,DOM樹層次結構向上到作用域根的跳躍次數最少的樣式獲勝。有關更多詳細資訊和示例,請參閱如何解決@scope衝突。
測試你的技能!
您已閱讀完本文,但您還記得最重要的資訊嗎?在繼續之前,您可以進行一些進一步的測試來驗證您是否保留了這些資訊——請參閱測試您的技能:級聯。
總結
如果您理解了本文的大部分內容,那麼恭喜——您已經開始熟悉CSS的基本機制。接下來,我們將深入瞭解級聯層。
如果您沒有完全理解級聯、特異性和繼承,請不要擔心!這絕對是我們到目前為止在課程中涵蓋的最複雜的事情,即使是專業的網頁開發人員有時也會發現它很棘手。我們建議您在繼續學習課程的過程中多次返回本文,並繼續思考它。
如果您開始遇到樣式未按預期應用的奇怪問題,請參考此處。這可能是一個特異性問題。