處理衝突
本節課旨在加深你對 CSS 中一些最基本概念的理解——層疊、優先順序和繼承——它們控制著 CSS 如何應用於 HTML 以及如何解決樣式宣告之間的衝突。
雖然學習本節課內容可能不像課程的其他部分那樣能立竿見影,甚至會感覺有點學術化,但理解這些概念將在以後為你免去很多麻煩!我們鼓勵你仔細學習本節內容,並在繼續學習前確保自己已理解這些概念。
| 預備知識 | HTML 基礎(學習基本的 HTML 語法),CSS 選擇器。 |
|---|---|
| 學習成果 |
|
規則衝突
CSS 是“層疊樣式表”(Cascading Style Sheets)的縮寫,第一個詞“層疊”(cascading)非常重要,需要深刻理解——層疊的運作方式是理解 CSS 的關鍵。
在某些時候,你會在專案中發現,你認為應該應用於某個元素的 CSS 並沒有生效。通常,這個問題發生在你建立了兩條規則,將同一屬性的不同值應用於同一元素時。
層疊(Cascade)以及與之密切相關的優先順序(specificity)概念,是控制在發生此類衝突時應用哪條規則的機制。為元素設定樣式的宣告可能不是你期望的那一個,所以你需要了解這些機制是如何工作的。
在這裡,繼承(inheritance)的概念也很重要,它指的是一些 CSS 屬性會預設繼承當前元素父元素上設定的值,而另一些則不會。這也可能導致意想不到的行為。
讓我們先快速瞭解一下我們將要處理的關鍵概念,然後逐一審視,看看它們如何相互作用以及如何影響你的 CSS。這些概念看起來可能難以理解,但隨著你編寫 CSS 的經驗越來越豐富,它們會變得越來越清晰。
層疊
樣式表是層疊的。簡單來說,這意味著 CSS 規則的來源和順序很重要。當兩條規則的優先順序相同時,樣式表中最後定義的那條規則將被使用。還有其他一些概念也會產生影響,比如層疊層,但這些內容更高階,我們在這裡不作詳細介紹。
在下面的例子中,我們有兩條規則可以應用於 <h1> 元素。<h1> 的內容最終變成了藍色。這是因為這兩條規則都來自同一個來源,擁有相同的元素選擇器,因此具有相同的優先順序,但源順序中最後的那條規則獲勝。
<h1>This is my heading.</h1>
h1 {
color: red;
}
h1 {
color: blue;
}
優先順序
優先順序是瀏覽器用來決定哪個屬性值應用於元素的演算法。如果多條規則有不同的選擇器,它們為同一元素設定了同一屬性的不同值,那麼優先順序將決定哪個屬性值被應用到該元素上。優先順序基本上是衡量一個選擇器的選擇範圍有多具體的標準。
- 型別(元素)選擇器的優先順序較低;它會選中頁面上出現的所有該型別的元素,所以它的權重較低。偽元素選擇器與普通元素選擇器具有相同的優先順序。
- 類選擇器的優先順序更高;它只會選中頁面上具有特定
class屬性值的元素,所以它的權重更高。屬性選擇器和偽類與類選擇器具有相同的權重。 - ID 選擇器的優先順序甚至更高——它只選中一個具有特定
id值的元素。因此,它的權重更大。
下面,我們又有兩條可以應用於 <h1> 元素的規則。<h1> 的內容最終變成了紅色,儘管 color: blue 宣告在源順序中出現得更晚,這是因為類選擇器 main-heading 使其規則的優先順序高於型別選擇器 h1。由類選擇器定義的、優先順序更高的宣告被應用了。
<h1 class="main-heading">This is my heading.</h1>
.main-heading {
color: red;
}
h1 {
color: blue;
}
我們稍後會解釋優先順序的計算演算法。
繼承
在這個背景下,繼承也需要被理解——一些設定在父元素上的 CSS 屬性值會被其子元素繼承,而另一些則不會。
例如,如果你為一個元素設定了 color 和 font-family,那麼它內部的每個元素也會被設定為該顏色和字型,除非你直接為它們應用了不同的顏色和字型值。
<p>
As the body has been set to have a color of blue this is inherited through the
descendants.
</p>
<p>
We can change the color by specifically targeting an element with a different
style, such as this
<span>span</span>.
</p>
body {
color: blue;
}
span {
color: black;
}
有些屬性不會被繼承——例如width。如果你為一個元素設定了 width 為 50%,它的所有後代元素不會都獲得其父元素 width 的 50%。如果真是這樣,使用 CSS 將會非常令人沮喪!
備註: 在 MDN 的 CSS 屬性參考頁面上,你可以找到一個名為“正式定義”的技術資訊框,其中列出了關於該屬性的許多資料點,包括它是否被繼承。例如,可以檢視 color 屬性的“正式定義”部分。
理解這些概念如何協同工作
這三個概念(層疊、優先順序和繼承)共同控制著哪條 CSS 應用於哪個元素。在下面的章節中,我們將看到它們如何協同工作。這有時可能看起來有點複雜,但隨著你對 CSS 越來越有經驗,你會開始記住它們,如果忘記了也可以隨時查閱細節!即使是經驗豐富的開發者也記不住所有的細節。
理解繼承
我們從繼承開始。在下面的例子中,我們有一個 <ul> 元素,裡面嵌套了兩層無序列表。我們為外部的 <ul> 設定了邊框、內邊距和字型顏色。
color 屬性是一個可繼承的屬性。因此,color 屬性值被應用到直接子元素和間接子元素上——即直接的 <li> 子元素和第一個巢狀列表中的那些。然後,我們為第二個巢狀列表添加了 special 類,併為其應用了不同的顏色。這個顏色隨後會向下繼承給它的子元素。
<ul class="main">
<li>Item One</li>
<li>
Item Two
<ul>
<li>2.1</li>
<li>2.2</li>
</ul>
</li>
<li>
Item Three
<ul class="special">
<li>
3.1
<ul>
<li>3.1.1</li>
<li>3.1.2</li>
</ul>
</li>
<li>3.2</li>
</ul>
</li>
</ul>
.main {
color: rebeccapurple;
border: 2px solid #cccccc;
padding: 1em;
}
.special {
color: black;
font-weight: bold;
}
像前面提到的 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。注意第二個連結是如何換行並帶有一個專案符號的。你認為哪些屬性被繼承了?
<ul>
<li>Default <a href="#">link</a> color</li>
<li class="my-class-1">Inherit the <a href="#">link</a> color</li>
<li class="my-class-2">Reset the <a href="#">link</a> color</li>
<li class="my-class-3">Unset the <a href="#">link</a> color</li>
</ul>
body {
color: green;
}
.my-class-1 a {
color: inherit;
}
.my-class-2 a {
color: initial;
}
.my-class-3 a {
color: unset;
}
重置所有屬性值
CSS 的簡寫屬性 all 可以用來將這些繼承值之一一次性應用於(幾乎)所有屬性。它的值可以是任何一個繼承值(inherit、initial、revert、revert-layer 或 unset)。這是一種便捷的方式,可以撤銷對樣式所做的更改,以便在開始新的更改之前回到一個已知的起點。
在下面的例子中,我們有兩個 blockquote。第一個為 blockquote 元素本身應用了樣式。第二個為 blockquote 應用了一個類,該類將 all 的值設定為 unset。
<blockquote>
<p>This blockquote is styled</p>
</blockquote>
<blockquote class="fix-this">
<p>This blockquote is not styled</p>
</blockquote>
blockquote {
background-color: orange;
border: 2px solid blue;
}
.fix-this {
all: unset;
}
嘗試將 all 的值設定為其他一些可用值,並觀察有什麼不同。
理解層疊
我們現在理解了,繼承是為什麼深嵌在 HTML 結構中的段落顏色與應用於 body 的 CSS 顏色相同的原因。透過前面的課程,我們已經瞭解瞭如何在文件的任何位置更改應用的 CSS——無論是透過為元素分配 CSS 還是透過建立類。現在,我們將探討當多個樣式塊對同一元素應用相同屬性但值不同時,層疊如何定義哪條 CSS 規則生效。
有三個因素需要考慮,按重要性遞增的順序列出。後面的因素會覆蓋前面的
- 源順序
- 優先順序
- 重要性
我們將逐一審視這些因素,看看瀏覽器究竟是如何確定應該應用哪些 CSS 的。
源順序
我們已經看到了源順序對層疊的重要性。如果你有多條規則,它們的權重完全相同,那麼 CSS 中最後出現的那條規則將獲勝。你可以這樣想:離元素本身越近的規則會覆蓋前面的規則,直到最後一條規則勝出併為元素設定樣式。
源順序只有在規則的優先順序權重相同時才起作用,所以接下來我們看看優先順序。
優先順序
你常常會遇到這樣的情況:你知道一條規則在樣式表中出現得更晚,但一條更早的、有衝突的規則卻被應用了。這是因為更早的那條規則具有更高的優先順序——它更具體,因此被瀏覽器選擇來為元素設定樣式。
正如我們在本節課前面看到的,類選擇器的權重高於元素選擇器,所以類樣式塊中定義的屬性會覆蓋元素樣式塊中定義的屬性。
這裡需要注意的是,雖然我們考慮的是選擇器以及應用於它們所選中的文字或元件的規則,但被覆蓋的不是整個規則,而只是在多個地方宣告的那些屬性。
這種行為有助於避免 CSS 中的重複。一個常見的做法是為基本元素定義通用樣式,然後為那些不同的元素建立類。例如,在下面的樣式表中,我們為二級標題定義了通用樣式,然後建立了一些只改變部分屬性和值的類。最初定義的值會應用於所有標題,然後更具體的值會應用於帶有這些類的標題。
<h2>Heading with no class</h2>
<h2 class="small">Heading with class of small</h2>
<h2 class="bright">Heading with class of bright</h2>
h2 {
font-size: 2em;
color: black;
font-family: "Georgia", serif;
}
.small {
font-size: 1em;
}
.bright {
color: rebeccapurple;
}
現在讓我們看看瀏覽器是如何計算優先順序的。我們已經知道,元素選擇器的優先順序低,可以被類選擇器覆蓋。基本上,不同型別的選擇器會被賦予一個分值,將這些分值相加就得到了該選擇器的權重,然後可以與其他潛在匹配的選擇器進行比較。
一個選擇器的優先順序大小由三個不同的值(或組成部分)來衡量,可以把它們看作是分別代表百、十、個位的 ID、CLASS 和 ELEMENT 列
- ID:對於整個選擇器中包含的每個 ID 選擇器,在這一列得一分(100 分)。
- 類:對於整個選擇器中包含的每個類選擇器、屬性選擇器或偽類,在這一列得一分(10 分)。
- 元素:對於整個選擇器中包含的每個元素選擇器或偽元素,在這一列得一分(1 分)。
下表展示了幾個獨立的例子,讓你感受一下。試著過一遍這些例子,確保你理解為什麼它們具有我們給出的優先順序。我們還沒有詳細介紹選擇器,但你可以在 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 |
深入優先順序示例
在我們繼續之前,讓我們看一個實際的例子。你可能想在單獨的標籤頁中用 MDN Playground 開啟它,以便在閱讀解釋時可以輕鬆地對照參考。
<div class="container" id="outer">
<div class="container" id="inner">
<ul>
<li class="nav"><a href="#">One</a></li>
<li class="nav"><a href="#">Two</a></li>
</ul>
</div>
</div>
/* 1. specificity: 1-0-1 */
#outer a {
background-color: red;
}
/* 2. specificity: 2-0-1 */
#outer #inner a {
background-color: blue;
}
/* 3. specificity: 1-0-4 */
#outer div ul li a {
color: yellow;
}
/* 4. specificity: 1-1-3 */
#outer div ul .nav a {
color: white;
}
/* 5. specificity: 0-2-4 */
div div li:nth-child(2) a:hover {
border: 10px solid black;
}
/* 6. specificity: 0-2-3 */
div li:nth-child(2) a:hover {
border: 10px dashed black;
}
/* 7. specificity: 0-3-3 */
div div .nav:nth-child(2) a:hover {
border: 10px double black;
}
a {
display: inline-block;
line-height: 40px;
font-size: 20px;
text-decoration: none;
text-align: center;
width: 200px;
margin-bottom: 10px;
}
ul {
padding: 0;
}
li {
list-style-type: none;
}
這裡發生了什麼?首先,我們只關心這個例子的前七條規則,正如你所注意到的,我們在每條規則前都用註釋標出了它們的優先順序值。
- 前兩個選擇器在爭奪連結的
background-color樣式。第二個獲勝,使背景色變為藍色,因為它在選擇器鏈中多了一個 ID 選擇器:其優先順序為 2-0-1,而另一個為 1-0-1。 - 第 3 和第 4 個選擇器在爭奪連結文字的
color樣式。第二個獲勝,使文字變為白色,因為雖然它少了一個元素選擇器,但缺少的選擇器被一個類選擇器替換了,而類選擇器的權重比元素選擇器高。獲勝的優先順序是 1-1-3,而另一個是 1-0-4。 - 第 5-7 個選擇器在爭奪連結懸停時的
border樣式。選擇器 6 明顯輸給了選擇器 5,優先順序為 0-2-3 對 0-2-4;它在選擇器鏈中少了一個元素選擇器。然而,選擇器 7 戰勝了選擇器 5 和 6,因為它與選擇器 5 在選擇器鏈中的子選擇器數量相同,但一個元素被換成了一個類選擇器。所以獲勝的優先順序是 0-3-3,而另外兩個是 0-2-3 和 0-2-4。
備註: 每種選擇器型別都有其自身的優先順序級別,不能被優先順序較低的選擇器覆蓋。例如,一百萬個類選擇器組合在一起也無法覆蓋一個 id 選擇器的優先順序。
評估優先順序的最佳方法是從最高級別開始逐個比較優先順序級別,必要時再比較下一個級別。只有當一個優先順序列中的選擇器分數打平時,才需要評估下一個更低的列;否則,你可以忽略優先順序較低的選擇器,因為它們永遠無法覆蓋優先順序較高的選擇器。
ID 與類
ID 選擇器具有很高的優先順序。這意味著基於匹配 ID 選擇器應用的樣式將覆蓋基於其他選擇器(包括類選擇器和型別選擇器)應用的樣式。因為一個 ID 在一個頁面上只能出現一次,並且 ID 選擇器的優先順序很高,所以最好給元素新增一個類而不是 ID。
如果使用 ID 是定位該元素的唯一方法——也許是因為你無法訪問和編輯標記——可以考慮在屬性選擇器中使用 ID,例如 p[id="header"]。
內聯樣式
內聯樣式,即 style 屬性內的樣式宣告,其優先順序高於所有普通樣式,無論優先順序如何。這類宣告沒有選擇器,但它們的優先順序可以被視為 1-0-0-0;總是高於任何其他優先順序權重,無論選擇器中有多少個 ID。
!important
有一段特殊的 CSS 可以用來覆蓋以上所有的計算,甚至是內聯樣式——那就是 !important 標誌。但是,在使用它時應該非常小心。這個標誌用於使單個屬性和值對成為最優先的規則,從而覆蓋層疊的正常規則,包括普通的內聯樣式。
備註: 瞭解 !important 標誌的存在是很有用的,這樣當你在別人的程式碼中遇到它時能知道它是什麼。然而,我們強烈建議你永遠不要使用它,除非你絕對必須這樣做。 !important 標誌改變了層疊的正常工作方式,所以它會使除錯 CSS 問題變得非常困難,尤其是在一個大型樣式表中。
看下面這個例子,我們有兩個段落,其中一個有 ID。
<p class="better">This is a paragraph.</p>
<p class="better" id="winning">One selector to rule them all!</p>
#winning {
background-color: red;
border: 1px solid black;
}
.better {
background-color: gray;
border: none !important;
}
p {
background-color: blue;
color: white;
padding: 5px;
}
讓我們逐步分析一下發生了什麼——如果你覺得難以理解,可以試著移除一些屬性看看會發生什麼。
- 你會看到第三條規則的
color和padding值被應用了,但background-color沒有。為什麼?按理說,所有這三條都應該被應用,因為源順序中靠後的規則通常會覆蓋靠前的規則。 - 然而,在它之上的規則獲勝了,因為類選擇器的優先順序高於元素選擇器。
- 兩個元素都有一個
better的class,但第二個還有一個winning的id。由於 ID 的優先順序比類更高,所以紅色的background-color和1px black的border都應該被應用到第二個元素上,而第一個元素則會得到類所指定的灰色背景色,並且沒有邊框。 - 第二個元素確實得到了紅色的
background-color,但沒有border。為什麼?因為第二條規則中的!important標誌。在border: none後面新增!important標誌意味著這個宣告將勝過前一條規則中的border值,即使 ID 選擇器具有更高的優先順序。
備註: 覆蓋一個重要宣告的唯一方法是在源順序的後面包含另一個具有相同優先順序的重要宣告,或者一個具有更高優先順序的重要宣告。
有一種情況你可能不得不使用 !important 標誌,那就是當你在一個 CMS 上工作,無法編輯核心 CSS 模組,而你又確實想要覆蓋一個無法用其他方式覆蓋的內聯樣式或重要宣告時。但說真的,如果能避免就不要用它。
CSS 位置的影響
最後,需要注意的是,CSS 宣告的優先順序取決於它在哪個樣式表中指定。
使用者可以設定自定義樣式表來覆蓋開發者的樣式。例如,一個視力受損的使用者可能希望將他們訪問的所有網頁的字型大小設定為正常大小的兩倍,以便於閱讀。
宣告覆蓋順序
衝突的宣告將按以下順序應用,後面的會覆蓋前面的
- 使用者代理樣式表中的宣告(例如,瀏覽器的預設樣式,在沒有設定其他樣式時使用)。
- 使用者樣式表中的普通宣告(使用者設定的自定義樣式)。
- 作者樣式表中的普通宣告(這些是我們,即 Web 開發者設定的樣式)。
- 作者樣式表中的重要宣告。
- 使用者樣式表中的重要宣告。
- 使用者代理樣式表中的重要宣告。
備註: 對於標記了 !important 的樣式,優先順序是反過來的。Web 開發者的樣式表覆蓋使用者樣式表是合理的,這樣可以保持設計的初衷;然而,有時使用者有充分的理由來覆蓋 Web 開發者的樣式,如上所述,這可以透過在他們的規則中使用 !important 來實現。
總結
如果你理解了本文的大部分內容,那麼做得很好——你已經開始熟悉 CSS 的基本機制了。
如果你沒有完全理解層疊、優先順序和繼承,也不用擔心!這絕對是我們在課程中迄今為止講過的最複雜的內容,即使是專業的 Web 開發者有時也會覺得棘手。我們建議你在繼續學習課程的過程中多回顧這篇文章,並不斷思考它。
如果你開始遇到樣式未按預期應用的奇怪問題,請回頭參考這裡。這可能是一個優先順序問題。接下來,我們會提供一些測試,你可以用它們來檢查你對我們提供的關於層疊的資訊的理解和記憶程度。