近幾年來 CSS 加入了許多 pseudo classes,而且不再像過去一樣只有少數的瀏覽器有實作。今天想要介紹幾個 CSS pseudo class,並透過實際案例幫助排版。
:is
:where
:has
:lang
:focus-within
:first-child
跟:last-child
:is
以往想要將相同 CSS 規則套用到多個選擇器時會這樣寫:
.a-class,
.b-class {
font-size: 25px;
}
這樣寫沒有任何問題,不過有 :is
之後我們可以將宣告簡化為:
:is(.a-class, .b-class) {
font-size: 25px;
}
如果要套用的選擇器很多的話,使用 :is
會更容易閱讀。雖然還在 Working Draft,不過就 Can I use 的數據來看,98.1% 的使用者都可以使用。
除了單一類別之外,也可以搭配其他選擇器使用:
:is(.a-class, .b-class) a:hover {
opacity: 0.8;
}
:where
:where
的作用和 :is
相同。不過在優先級的部分有差別。使用 :where
時是沒有權重的,優先級與 *
相同。然而 :is
不太一樣,會根據列表中優先級最高的權重為最後權重。
Hello
在這個範例當中,因為有 #page
的關係,權重大於類別選擇器,因此最後的顏色會是紅色。如果使用 :where
的話,裡頭的選擇器不會有額外權重。
Hello
因此最後套用的顏色會是黃色。 從上面的結果看來,使用場景可分為:
- 透過
:where()
來做 global 樣式(例如 reset 等等),這樣要修改時方便直接覆蓋 - 透過
:is()
對多個選擇器做 local 樣式,確保全部的選擇器都套用同一個權重
:has
:has
可以匹配當符合條件的子元素成立時,匹配父元素。說明聽起來有點繞口,直接用範例會比較清楚一點。一個很實用的場景是「當子元素被 focus 時想要改變父元素的樣式」。
有時候實作搜尋功能時,我們會將搜尋 icon 放在輸入框旁邊,當輸入框被 focus 時,我們希望不只輸入框套用 focus 的樣式而已,而是整個輸入框包含 icon 的部分一起套用 focus 的樣式。這時候用 input:focus
就沒辦法達成我們想要的效果。
過去的解決方式是修改 DOM 的結構讓 CSS 選擇器生效,或是透過 JavaScript 監聽 focus 事件後加入類別讓父元素知道 input 已經被 focus 了。而透過 :has
我們可以直接在 CSS 裡達成而不需要改變結構。
Focus input!
這樣一來當 input 被 focus 時,.form:has(input:focus)
就會生效,將樣式套用在父元素上。能夠達到「當子元素符合條件時匹配父元素」這件事情是前端工程師長久以來的願望,現在終於往前邁進一大步了。
:has
還有其他用法,特別是想要達成「子元素符合某條件時套用此選擇器」的場景。
例如在切換主題時,透過 body:has(input[type="checkbox"]:checked)
就可以達到不用 JavaScript 來切換顏色主題,同時保有合理的 DOM 結構。
關於 :has
的使用場景,在 webkit 的部落格中有更多使用場景可以參考。:has
是相對必較新的 pseudo class,瀏覽器支援程度還沒有那麼高(82.92%),要導入的話可能還是要有備案。
:focus-within
對某些比較有經驗的前端工程師來說,應該有發現剛剛的範例其實可以用 :focus-within
來取代吧。:focus-within
可以在子元素被 focus 時匹配,這樣一來就可以修改父元素的樣式。
例如在 focus 時想要將表單加入 box-shadow
就可以這樣寫:
不過 :focus-within
只能套用在 focus 上,不能做出多樣性的選擇,如果需要匹配不是 focus 的情況是就可以使用更有彈性的 :has
。
:lang
在實作多國語言網站時,會將 <html>
加入 lang
標籤來表示目前網站語言為何。有時候我們可能要針對不同語系顯示不同字體,在不使用 :lang
的情況下可能需要這樣寫:
html[lang="zh"] p { font-family: var(--font-zh); }
html[lang="en"] p { font-family: var(--font-en); }
寫起來比較冗長一點,透過 :lang
可以簡化選擇器語法。
p:lang(en) {
font-family: var(--font-en);
}
p:lang(zh) {
font-family: var(--font-zh);
}
:first-child 與 :last-child
這兩個雖然是很常見的 pseudo class,不過我發現還是有蠻多前端工程師不知道的。這兩個 pseudo class 可以指定到第一個 child 或是最後一個 child,最常見的場景就是指定 margin-right
時不想要套用到最後一個元素上,這時候就可以這樣寫。
.tab {
margin-right: 8px;
}
.tab:last-child {
margin-right: 0;
}
也可以搭配 :not
來使用來簡化:
.tag:not(:last-child) {
margin-right: 8px;
}
為什麼不用 JavaScript 就好?
有些使用者不一定會開啟 JavaScript,尤其是像是部落格等靜態文章居多的網站,但又不想要喪失太多閱讀體驗時,上述介紹到的 Pseudo class 可以幫助我們解決排版問題。如果可以簡單誰想要複雜呢?