Kalan's Blog

Kalan 頭像照片,在淡水拍攝,淺藍背景

四零二曜日電子報上線啦!訂閱訂起來

本部落主要是關於前端、軟體開發以及我在日本的生活,也會寫一些對時事的觀察和雜感
本部落格支援 RSS feed(全文章內容),可點擊下方 RSS 連結或透過第三方服務設定。若技術文章裡有程式碼語法等特殊樣式,仍建議至原網站瀏覽以獲得最佳體驗。

目前主題 亮色

我會把一些不成文的筆記或是最近的生活雜感放在短筆記,如果有興趣的話可以來看看唷!

排版時有用的 Pseudo 類別

近幾年來 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 不太一樣,會根據列表中優先級最高的權重為最後權重。

在這個範例當中,因為有 #page 的關係,權重大於類別選擇器,因此最後的顏色會是紅色。如果使用 :where 的話,裡頭的選擇器不會有額外權重。

因此最後套用的顏色會是黃色。 從上面的結果看來,使用場景可分為:

  • 透過 :where() 來做 global 樣式(例如 reset 等等),這樣要修改時方便直接覆蓋
  • 透過 :is() 對多個選擇器做 local 樣式,確保全部的選擇器都套用同一個權重

:has

:has 可以匹配當符合條件的子元素成立時,匹配父元素。說明聽起來有點繞口,直接用範例會比較清楚一點。一個很實用的場景是「當子元素被 focus 時想要改變父元素的樣式」。 有時候實作搜尋功能時,我們會將搜尋 icon 放在輸入框旁邊,當輸入框被 focus 時,我們希望不只輸入框套用 focus 的樣式而已,而是整個輸入框包含 icon 的部分一起套用 focus 的樣式。這時候用 input:focus 就沒辦法達成我們想要的效果。 過去的解決方式是修改 DOM 的結構讓 CSS 選擇器生效,或是透過 JavaScript 監聽 focus 事件後加入類別讓父元素知道 input 已經被 focus 了。而透過 :has 我們可以直接在 CSS 裡達成而不需要改變結構。

這樣一來當 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 可以幫助我們解決排版問題。如果可以簡單誰想要複雜呢?

上一篇

用 Next.js 重寫整個部落格

下一篇

HTML 和 CSS 能解決很多問題,但 JS 也很重要

如果覺得這篇文章對你有幫助的話,可以考慮到下面的連結請我喝一杯 ☕️ 可以讓我平凡的一天變得閃閃發光 ✨

Buy me a coffee