在本章中作者主要講解了 CSS 如何對 HTML 添加樣式的,并且解釋了層疊的工作機制比如當元素的同一個屬性被多次設置樣式后到底應該選擇何種樣式,這就要靠 CSS 的層疊機制來決定最終應用哪種樣式了。
每個 HTML 元素都有一組樣式屬性,這些屬性涉及元素在文檔流中顯示時的不同方面,比如在文檔流中的位置、邊框、背景、顏色等等。CSS 就是一種先選擇 HTML 元素,然后設定選中元素 CSS 屬性的機制。CSS 選擇符和要應用的樣式構成一條 CSS 規則。
## 2.2 上下文選擇器
上下文選擇器的格式如下:
~~~
標簽1 標簽2 { 聲明}
~~~
其中標簽2就是我們要選擇的目標,而且只有在標簽1是標簽2的祖先元素(不一定是父級元素)的情況下才會被選中。上下文選擇器嚴格來講應該叫「后代組合式選擇器(Descendant Comninator Selector)」。
還有一點要注意的是,上下文選擇器以空格作為分隔符,而分組選擇器則以逗號作為分隔符,不要弄混。
## 2.3 特殊的上下文選擇器
前面一節作者介紹的上下文選擇器是以某個祖先元素作為上下文的,只要目標元素在 DOM 結構「上游」存在這么一個祖先元素即可,無論這個祖先元素和目標元素隔了多少層級都沒有關系,但有的時候我們需要比「某個祖先元素」更具體的上下文,這時候我們就可以使用一些特殊的選擇器了,比如自選擇器?`>`、 緊鄰兄弟選擇器`+`、一般兄弟選擇器?`~`?和通用選擇器?`*`。
### 2.3.1 子選擇器 >
~~~
標簽1 > 標簽2
~~~
這里的標簽2必須是標簽1的子元素,也就是說標簽1必須是標簽2的父元素,而不能是標簽2的任何其他祖先元素。
### 2.3.2 緊鄰兄弟選擇器 +
~~~
標簽1 + 標簽2
~~~
在這里標簽2必須緊跟在兄弟標簽1的后面,否則無效。
### 2.3.3 一般兄弟選擇器 ~
~~~
標簽1 ~ 標簽2
~~~
在這里標簽2必須跟(不一定要緊跟,只需在標簽1的后面即可)在其兄弟標簽1后面。
### 2.3.4 通用選擇器 *
~~~
*
~~~
通用選擇器?`*`?是一個是一個通配符,代表文檔流中的任意元素,不過通用選擇器?`*`?通常會搭配一些其他選擇器來使用,比如:
~~~
section > *
~~~
代表?`section`?的所有子元素,不過一般情況下很少通過通配符來選擇某個元素下的所有子元素,因為這涉及到瀏覽器性能問題,它會影響網頁的渲染時間,我們寫的時候是從左到右寫的,但是瀏覽器渲染卻是從右到左的,就上面這段代碼來說,瀏覽器會先遍歷所有的元素,然后在找出哪些元素的父元素是?`section`,另外舉一個例子,有選擇器:
~~~
div.container #main > .article {}
~~~
瀏覽器在渲染時,先把所有類中包含?`article`?的元素取出來組成一個集合,然后對每一個集合中的元素進行遍歷,如果元素的父元素的?`id`?不為?`main`?則把元素從集合中刪去。 再然后從這個元素的父元素開始向上找,沒有找到一個標簽名為?`div`?并且類名中有?`container`?的元素,就把元素從集合中刪去,直到匹配所有的條件,所以在能不使用通配符的情況就盡量不要使用它。
## 2.4 ID 和類選擇器
作者在這一節介紹了?`id`?和?`class`?選擇器,為我們選擇元素提供了另一種手段,利用它們可以不考慮元素在文檔流中的層次結構,只要在元素中添加了?`id`?和?`class`?屬性和值,我們就可以通過它們的值來找到目標元素。
> 可以給?`id`?和?`class`?屬性設定任何值,但不能以數字或者特殊符號開頭。
### 2.4.3 什么時候用 id,什么時候用 class
`id`?的用途是在頁面中唯一地標識元素,所以每個頁面中每一個?`id`?屬性值都是獨一無二的。而?`class`?的目的是為了標識一組具有相同特征的元素,也就是說一個頁面中可以出現多個相同的類。
對于什么時候用?`id`?這個問題作者的觀點是:
> 每一個頂級區域都應該添加一個?`id`,從而得到非常明確的上下文關系,以便編寫 CSS 時只選擇嵌套在相應區域內的標簽。
對于什么時候使用?`class`,由于?`class`?的目的是為了標識一組具有相同特征的元素,所以如果當頁面中有一組元素具有某種相同的特征,就應該毫不猶豫的時候?`class`了。
但是這里也應該注意不要亂用類,避免造成類泛濫,例如:
~~~
<nav>
<ul>
<li class="boy"><a href="#">Alan</a></li>
<li class="girl"><a href="#">Andrew</a></li>
<li class="boy"><a href="#">Angela</a></li>
<li class="boy"><a href="#">Angus</a></li>
<li class="girl"><a href="#">Anne</a></li>
<li class="girl"><a href="#">Annette</a></li>
</ul>
</nav>
~~~
上面這個例子就是一個典型的類泛濫。
### 2.4.4 id 和 class 的小結
對于什么時候用 id 和什么時候用 class,我想每個人都有不同的看法,這里寫說一下筆者的觀點,筆者認為能不實用?`id`?就盡量不使用?`id`,實際情況是筆者基本不在 CSS 中使用?`id`,因為在 CSS 的層疊機制中,`id`?的權重是?`class`?的10倍,其實很多情況下對某個元素設置某個不一樣的樣式來覆蓋之前的樣式并沒有效果就是因為之前的樣式權重太高,而為了達到效果就要編寫權重更高的選擇器,所以只有在某個元素需要被 JavaScript 找到的時候才會在某個元素中添加?`id`?,以便可以通過`document.getElementById()`?方法來快速獲取需要的元素。
## 2.5 屬性選擇器
屬性選擇器包括屬性名選擇器和屬性值選擇器,它們是通過元素的屬性和值來獲取元素的:
~~~
標簽名[屬性名]
標簽名[屬性名="屬性值"]
~~~
例如:
~~~
img[title] {border: 2px solid blue;}
a[target="_blank"] {background-image: url(_blank.png);}
~~~
上面第一段代碼意思是,如果某個?`img`?標簽帶有?`title`?這個屬性,那么就為它添加一個寬度為 2px 的藍色實線邊框。第二段代碼的意思是,如果某個?`a`?標簽帶有`target`?這個屬性,并且這個屬性的值為?`_blank`?那么就為這個元素添加一個背景圖。
拓展:
其實除了以上兩種屬性選擇器,還有其他幾種屬性選擇器作者并沒有列出來,這里這幾種其他的屬性選擇器作一個簡單的介紹:
~~~
標簽名[name^="value"] 讓你匹配屬性為 `name` 并且屬性值以 `value` 開始的標簽,如:a[href^= "http://"]則匹配所有具有 `href` 屬性并且屬性值以 `http://` 開始的標簽。
標簽名[name$="value"] 讓你匹配屬性為 `name` 并且屬性值以 `value` 結束的標簽,如:a[href$=".com"]則匹配所有具有 `href` 屬性并且屬性值以 `http://` 結束的標簽。
標簽名[name*="value"] 讓你匹配屬性為 `name` 并且屬性值包含 `value` 的標簽,如:a[href*= "renren"]則匹配所有具有 `href` 屬性并且屬性值包含 `http://` 的標簽。
標簽名[name|="value"] 讓你匹配屬性為 `name` 或者以 `name-` 開始的標簽,如:p[lang|= "en"]則匹配具有 `lang` 屬性的 `p` 標簽,不管其屬性值是 `en` 還是 `en-us` 。
標簽名[name~="value"] 讓你匹配屬性為 `name` 并且其屬性值是具有多個空格分隔的值,其中一個值為 `value`,如有:
<a title="I'm title for learn more">Learn More</a>
就可以用 p[title~="learn"] 來選擇這個元素。
~~~
你應該注意到了這些屬性選擇器與前面兩種屬性選擇器之間的差別了,通過這些屬性選擇器我們可以很容易的做出許多意想不到的效果,比如:
~~~
a[href$=".pdf"] {background-image: url(pdf.png);}
~~~
比如上面這段代碼就為鏈接是 pdf 文檔連接的?`a`?標簽添加一個表示這個鏈接是 pdf 文檔的圖片,而其他?`href`?屬性的值不是以?`.pdf`?結尾的?`a`?標簽就不會應用這條樣式聲明,讓用戶很清楚的判斷這是一個什么類型的鏈接。
## 2.6 偽類
偽類這個叫法源自它們與類相似,但實際上并沒有類會附加到標記中的標簽上,偽類分為兩種:
* UI(User Interface,用戶界面)偽類:會在 HTML 元素處于某個狀態時(比如鼠標指針位于連接上),為該元素應用 CSS 樣式。
* 結構化偽類:會在標記中純在某種結構上的關系時(比如某個元素是一組元素的第一個或者最有一個元素),為相應的元素應用 CSS 樣式。
### 2.6.1 UI偽類
1. 鏈接偽類
* link: 鏈接就在那兒等著用戶點擊。
* visited:用戶此前點擊過這個鏈接。
* hover:鼠標指針正懸停在連接上。
* active:鏈接正在i被點擊(鼠標在元素上按下,還沒有釋放)。
注意以上幾種鏈接偽類要按一定的順序才有效果,為了方便記憶作者是這么建議的:”LoVe?HA!”,大寫字母就是每個偽類的第一個字母,其實也可以這么記: “LoVe,HAte”,其實都差不多就是了。
> 一個冒號(:)表示偽類,兩個冒號(::)表示 CSS3 新增的偽元素。
2. :focus 偽類表單中的文本字段在用戶單擊它時會獲得焦點,例如:
~~~
input:focus {border: 1px solid blud;}
~~~
這段代碼的意思就是當用戶單擊表單中的文本字段的時候,為該?`input`?標簽添加寬度為 1px 的藍色實線邊框,需要注意的是,偽類的冒號要緊跟著標簽名,之間不能有空格,否則該聲明無效。
3. :target 偽類如果用戶點擊一個指向頁面中其他元素的鏈接,則哪個元素就是目標(target),可以用?`:target`?偽類選中它,比如:
~~~
<a href="#more-info">More Information</a>
~~~
位于頁面其他地方、`id`?為?`more-info`?的那個元素就是目標元素,該元素可能是這樣的:
~~~
<h2 id="more=info">This is the information you are looking for.</h2>
~~~
那么 CSS 規則如下:
~~~
#more-info:target {background: #eee;}
~~~
此時會在用戶點擊鏈接轉向?`id`?為?`more-info`?的元素時,該目標元素的背景就會變成淺灰色。
### 2.6.2 結構化偽類
1. first-child 、 last-child 和 nth-child(n)
~~~
e:first-child
e:last-child
~~~
`first-child`?和?`last-child`?分別代表一組同胞元素中的第一個元素和最后一個元素,而?`nth-child(n)`?則代表一組同胞元素中的任何一個元素,其中 n 表示一個整數(也可以是 odd-奇數 或 even-偶數)或者也可以是一個算數表達式(2n + 1),例如:
~~~
<ul>
<li>My Fast Pony</li>
<li>Steady Trotter</li>
<li>Slow Ol' Nag</li>
</ul>
ul li:first-child {color: black;}
ul li:nth-child(2) {color: red;}
ul li:last-child {color: blue;}
~~~
上面的 HTML 應用了上面的 CSS 規則后,無序列表的第一個元素字體顏色就會變成黑色,第二個元素變成紅色,最后一個元素就變成藍色。
## 2.7 偽元素
顧名思義,偽元素就是文檔中若有實無的元素,下面是幾個比較常用的偽元素。
1. ::first-letter 偽元素,比如:
~~~
p::first-letter {font-size: 300%;}
~~~
這樣?`p`?標簽的第一個字母大小就會變成原來的 3 倍了,而其他元素則不會。
2. ::first-line 偽元素:可以選中文本段落的第一行。
3. ::before 和 ::after 偽元素
~~~
e::before
e::after
~~~
可用在特定元素前面或后面添加特殊內容,比如:
~~~
<p class="age">25</p>
.age::before { content: "Age: ";}
.age::after { content: " years";}
~~~
這里需要注意的是,對于?`::before`?和?`::after`?偽元素,其?`content`?屬性是必須的,還有就是搜索引擎不會取得偽元素的信息(因為它在文檔流中并不存在),因此不要通過偽元素添加一些對搜索引擎來說是重要的內容。
拓展:
其實偽元素前面冒號可以是兩個也可以是一個,但是為了區別偽類,筆者建議大家還是使用兩個冒號。還有一個要注意的是,比如通過?`::before`?和?`::after`?偽元素為`class`?為?`pseudo-element`?添加兩個偽元素,則生成的兩個偽元素分別處于?`pseudo-element`?元素的內部,也就是說是?`pseudo-element`?元素的子元素,并且分別位于`pseudo-element`?元素的內容的最前面和最后面,代碼如下:
~~~
<div class="pseudo-element">
<p>Pseudo Element</p>
</div>
.pseudo-element::after,
.pseudo-element::before {
content: "";
}
~~~

如上圖所示,生成的兩個偽元素分別處于?`pseudo-element`?元素的內部,并且分別位于?`pseudo-element`?元素的內容?`p`?標簽的前面和后面,而不是如下圖所示的位于`pseudo-element`?元素外部的前面和后面:

## 2.9 層疊
層疊就是層疊樣式表中的層疊,是一種樣式在文檔層次中逐層疊加的過程,目的是讓瀏覽器面對某個標簽特定屬性值的多個來源確定最終使用哪個值。
層疊是 CSS 的核心機制,理解了它才能以最經濟的方式寫出最容易改動的 CSS,讓文檔外觀在達到設計要求的同時,也給用戶留下一些空間,讓他們能根據需要更改文檔的顯示效果。
### 2.9.1 樣式來源
作者在這一節中介紹了樣式的幾種來源:
~~~
- 瀏覽器默認樣式表
- 用戶樣式表
- 作者鏈接樣式表
- 作者嵌入樣式
- 作者行內樣式
~~~
作者在書中是這么描述的:
> 瀏覽器會按照上面的順序依次檢查每個來源的樣式,并在有定義的情況下,更新對每個標簽屬性值的設定,整個檢查更新過程結束后,再將每個標簽以最終設定的樣式顯示出來。
### 2.9.4 計算特指度
作者在這一節主要介紹了特指度的計算方法,相比作者個計算方式,筆者個人還是比較喜歡自己之前的計算方式,雖然差不多,如下:
~~~
// 首先規定四個等級:A - B - C - D
1\. A 等級代表內聯樣式:例如 `style=" "`,權值為:1000;
2\. B 等級代表 ID 選擇器:例如 `#main`,權值為:100;
3\. C 等級代表類、偽類和屬性選擇器: `.class` 和 `[title]`,權值為:10;
4\. D 等級代表元素(標簽)名或者偽元素選擇器:例如 `p` 和 `::after`,權值為:1。
//計算完每個值后再將每個值加起來,哪個值大哪個值的權重就高。
~~~
例如:
~~~
body #main .class a[title]::after {}
~~~
我們先分析它由哪些選擇器構成,上面這條規則有一個?`id`?選擇器(`#main`),一個類選擇器(`.class`),一個屬性選擇器(`[title]`)、一個偽元素選擇器(`::after`)和兩個標簽名選擇器(`body`?和?`a`),所以它的權重就等于:
~~~
100 × 1 + 10 × 2 + 1 × 3 = 123
~~~
還有一點要注意的是,權重值 001(12) 與 0020 相比,任然是 0020 的權重更高,對于權重一樣的情況,則后聲明的樣式更高。
## 2.10 小結
作者在本章介紹了 CSS 的一些規則,比如各種選擇器的使用,層疊機制,還有權重的計算。
如果有不對或不理解的地方歡迎指出和討論。