Learning Objectives
- 用元素、class、id、全域與屬性選擇器選中元素,並用逗號把規則套到多個選擇器
- 分辨子孫(空白)、子(>)、相鄰兄弟(+)、一般兄弟(~)四種組合器
- 用 :hover、:focus、:first-child、:nth-child()、:not() 依狀態或位置選擇元素
- 用 ::before、::after、::first-line 替元素的一部分加樣式,並知道 ::before 需要 content
- 依用途選擇 class 或 id,用描述意義的名稱命名
選擇器決定規則套用在誰身上
上一課的規則都很單純:h1 { … } 套用到所有 h1。但真實的網頁常常需要更精準:
- 導覽列裡的連結和文章裡的連結長得不一樣。
- 只有「熱門」的標籤要紅色,其他標籤維持灰色。
- 表格的偶數列加底色,方便眼睛對齊。
- 連到外部網站的連結後面加一個小圖示。
這些都不需要改 HTML,只要寫出剛好選中那些元素的選擇器(selector)。這一課把常用的選擇器分成四類: 基本選擇器、組合器(看元素之間的關係)、虛擬類別(看位置或狀態)、虛擬元素(選元素的一部分)。
基本選擇器
| 選擇器 | 寫法 | 選中 |
|---|---|---|
| 元素(type) | p |
所有 <p> |
| class | .tag |
所有 class 屬性裡有 tag 的元素 |
| id | #music |
id="music" 的那一個元素 |
| 全域(universal) | * |
所有元素 |
| 屬性(attribute) | [target] |
有 target 屬性的元素 |
一個元素可以有好幾個 class,用空白隔開:<span class="tag hot">。選擇器可以把條件連在一起寫(中間不留空白),表示「同時符合」:
span.tag.hot 是「class 同時有 tag 和 hot 的 span」。
屬性選擇器還能比對屬性值:
| 寫法 | 意思 | 例子 |
|---|---|---|
[href="https://example.com"] |
值完全相等 | |
[href^="https"] |
值開頭是 https | 外部連結 |
[href$=".pdf"] |
值結尾是 .pdf | 下載檔案的連結 |
[href*="example"] |
值包含 example | |
[class~="hot"] |
值用空白分開的字裡有 hot(和 .hot 一樣) |
群組:幾個選擇器要套同一組宣告時,用逗號隔開,不必重寫:
h1, h2, h3 { color: #3C6F9A; } /* 三種標題都套用 */
組合器:依元素之間的關係選擇
HTML 是一棵樹(第 2 課的 DOM 樹)。組合器(combinator)寫在兩個選擇器中間,說明它們在樹上的關係:
| 組合器 | 寫法 | 選中 | 例子 |
|---|---|---|---|
| 子孫(descendant) | A B(空白) |
A 裡面任何一層的 B | nav a:導覽列裡所有的連結 |
| 子(child) | A > B |
A 下一層的 B | .links > li:只有第一層清單項目 |
| 相鄰兄弟(next-sibling) | A + B |
緊接在 A 後面的那一個 B | h2 + p:標題正下方的第一段 |
| 一般兄弟(subsequent-sibling) | A ~ B |
A 後面同一層的所有 B | h2 ~ p:標題後面的每一段 |
用社團活動週的頁面來看:
<nav class="menu">
<ul>
<li><a href="#music">音樂</a></li>
</ul>
</nav>
.menu a選得到連結:a 在 nav 裡面(隔了 ul、li 兩層也算)。.menu > a選不到:nav 的下一層是 ul,不是 a。
選擇器是由右往左讀的:.menu a 先找所有的 a,再看它有沒有一個祖先是 .menu。最右邊那一個才是真正被選中、套用樣式的元素。
小測驗:基本選擇器與組合器
HTML 是 <nav class="menu"><ul><li><a href="#">音樂</a></li></ul></nav>。哪個選擇器選得到那個 a?
h1, h2 { color: navy; } 的意思是?
a[href$=".pdf"] 選得到下面哪一個連結?
虛擬類別與虛擬元素
虛擬類別(pseudo-class)以一個冒號開頭,依元素的狀態或位置選擇,這些資訊不寫在 HTML 裡:
| 虛擬類別 | 選中 |
|---|---|
:hover |
滑鼠移到上面的元素 |
:focus |
目前得到焦點的元素(點選輸入框、用 Tab 移過去) |
:first-child/:last-child |
在兄弟裡排第一個/最後一個 |
:nth-child(2) |
排第 2 個;(odd) 奇數、(even) 偶數、(3n) 每 3 個 |
:not(.hot) |
不符合括號裡條件的元素 |
.schedule tr:nth-child(even) { background-color: #f1f5f9; } /* 斑馬紋 */
a:hover { text-decoration: none; }
input:focus { outline: 3px solid #3C938A; }
:hover 和 :focus 要成對想:用鍵盤操作的人沒有滑鼠,只靠 :focus 看得出自己在哪裡。不要把 focus 的外框拿掉而不給替代樣式。
虛擬元素(pseudo-element)以兩個冒號開頭,選的是元素的一部分,或在元素前後產生內容:
| 虛擬元素 | 選中 |
|---|---|
::before/::after |
在元素內容的最前面/最後面產生一段內容,必須搭配 content |
::first-line |
第一行(隨視窗寬度改變) |
::first-letter |
第一個字 |
::placeholder |
輸入框的提示文字 |
.required::after { content: " *"; color: #B5524C; }
::before、::after 產生的內容不在 HTML 裡,螢幕閱讀器不一定會唸,所以只用在裝飾性的東西;
例如必填的星號只是視覺提示,欄位本身仍要加 required 屬性(第 7 課)。
class 還是 id?怎麼命名?
| class | id | |
|---|---|---|
| 一頁可以出現幾次 | 很多次,一個元素也可以有好幾個 class | 只能一次,一頁裡不能重複 |
| 主要用途 | 套樣式:同一種外觀的元素 | 頁內錨點(第 4 課的 #music)、表單 label 的 for、JavaScript 找特定元素 |
| 選擇器的強度 | 普通 | 很強,之後很難覆蓋(下一課的權重) |
所以樣式一律用 class,id 留給錨點和 label。
命名的習慣:
- 描述用途,不描述外觀:
.note、.tag、.btn-primary比.yellow-box、.red-text好;之後把提醒改成藍色,.yellow-box就名不符實了。 - 全部小寫,多個字用連字號連接(kebab-case):
.club-card、.nav-link。 - 元件裡的部分可以用「元件名稱+部分」:
.club-card、.club-card-title。有些團隊用 BEM 這類命名規則(.card__title、.card--featured),重點是整個專案用同一套。 - 選擇器不要寫太長:
body main section.club div p span.tag很難讀,而且 HTML 結構一改就失效;.club .tag通常就夠了。
小測驗:虛擬類別、虛擬元素與命名
一份清單有 5 個 li,li:nth-child(odd) 會選到哪幾個?
section 裡依序是 h2、p、p、ul。h2 + p 選到什麼?
寫了 .new::before { color: red; },畫面上卻什麼都沒出現。最可能的原因是?
活動頁有 6 張外觀相同的社團卡片,要替它們加樣式。用什麼比較好?
重點整理
- 基本選擇器:元素
p、class.tag、id#music、全域*、屬性[href^="https"];條件連寫表示同時符合,逗號表示群組。 - 組合器:空白(任何一層的子孫)、
>(下一層)、+(緊接在後的兄弟)、~(後面所有兄弟);選擇器由右往左讀,最右邊是被選中的元素。 - 虛擬類別看狀態與位置:
:hover、:focus、:first-child、:nth-child()、:not();不要拿掉 focus 外框而不給替代。 - 虛擬元素選元素的一部分:
::before/::after要搭配content,只放裝飾性內容。 - 樣式用 class,id 留給錨點與 label;名稱描述用途,不描述外觀。
下一課講層疊與權重:同一個元素被好幾條規則選中、而且設定互相衝突時,瀏覽器怎麼決定聽誰的。