精讀《使用 CSS 屬性選擇器》

黃子毅發表於2018-11-26

1 引言

雖然現在 Css Module 與 Css-in-js 更流行,但使用它們會導致過分依賴 濫用 class 做唯一定位,違背了 Css 選擇器的初衷。

本期精讀的文章是:attribute-selectors-splicing-html-dna-css,帶你重新理解強大的 Css 選擇器。

2 概要

Css Module 與 Css-in-js 大部分場景使用 className 作為選擇器,那麼本文以選擇器為重點,看看選擇器有哪些實用的用法。

屬性選擇器

如果你想選擇包含 title 屬性的 div

div[title]
複製程式碼

選擇包含 title 屬性的子元素,只需要加個空格:

div [title]
複製程式碼

選擇 title 內容是 dna 的元素:

div[title="dna"]
複製程式碼

選擇 title 屬性包含 dna 單詞的元素:

注意 dna 需要是單詞,也就是用空格分割,比如 “my beautiful dna” 或 “mutating dna is fun!”

div[title~="dna"]
複製程式碼

和正則類似,選擇 title 屬性中,以 dna 結尾的元素:

div[title$="dna"]
複製程式碼

dna 開頭:

div[title^="dna"]
複製程式碼

如果希望選擇 dnadna-zh,但不希望匹配 dnaer,可以:

這種場景一般用在國際化,比如 en en-us 就可以用 |="en"

div[title|="dna"]
複製程式碼

只要包含 dna 這三個字元就選中:

div[title*="dna"]
複製程式碼

真的很像正則,你可以用 i 標識匹配時大小寫不敏感:

div[title*="dna" i]
複製程式碼

如果你想找到一個 a 標籤,擁有 title 屬性並且 className 以 genes 結尾,可以這樣:

a[title][class$="genes"]
複製程式碼

獲取標籤的值

可以用 attr 識別符號拿到當前選擇器選中元素的屬性,比如當 hover 狀態時,在文字尾部顯示其 title 屬性:

.joke:hover:after {
  content: "Answer:" attr(title);
  display: block;
}
複製程式碼

其它用法

本文還介紹了一些實用技巧,比如

根據輸入框型別設定樣式

input[type="email"] {
  color: papayawhip;
}
input[type="tel"] {
  color: thistle;
}
複製程式碼

改變下載標籤的 icon

a[download][href$="pdf"]:after {
  content: url(pdf-icon.svg);
}
複製程式碼

當然也可以選中一些老程式碼進行樣式重寫,比如:

<div bgcolor="#000000" color="#FFFFFF">Old, holey genes</div>
複製程式碼
div[bgcolor="#000000"] {
  /*override*/
  background-color: #222222 !important;
}
複製程式碼

不過這種用法要謹慎,寫的越多越難以維護。

結合一些新標籤功能

比如 details 標籤是 html 原生的手風琴摺疊元件:

<details> <summary>List of Genes</summary> Roddenberry Hackman </details>
複製程式碼

我們可以使用屬性選擇器,定義其開啟時的樣式:

details[open] {
  background-color: hotpink;
}
複製程式碼

為沒有 async 標記的 script 標籤著色,算是友情提示哪兒有錯誤:

script[src]:not([async]) {
  display: block;
  width: 100%;
  height: 1em;
  background-color: red;
}
script:after {
  content: attr(src);
}
複製程式碼

為 JS 事件著色,比如觸發的滑鼠事件可以作為選擇器:

[OnMouseOver] {
  color: burlywood;
}
[OnMouseOver]:after {
  content: "JS: " attr(OnMouseOver);
}
複製程式碼

選中隱藏元素:

[hidden],
[type="hidden"] {
  display: block;
}
複製程式碼

還有更多就不一一列舉了,感興趣的讀者可以跳轉到原文繼續閱讀。大部分內容其實都寫在了 w3school 選擇器參考手冊,只是結合一篇文章來讀,可以理解得更深刻,同時文章裡確實有一些新鮮的選擇器,比如 JS 事件選擇器,HTML5 屬性標籤選擇器等等。

3 精讀

這篇文章確實說明了 Css 選擇器的強大性,但回到 css module 或者 css-in-js 的工程程式碼裡,我們往往難以做太多的實踐,有如下幾個原因:

一直在擔心的 DOM 結構變動

業務開發中,大量需求湧入,也許過了一週,DOM 結構就已經面目全非了,而且就算是一個普通的聖盃佈局,可能老版本用 Table 佈局,後面進來一個年輕小夥子直接用 div + flex 重構了,你會擔心之前寫的 table 選擇器在某一天全部失效。

也許今天的 div 選擇器,明天因為語義化改造就換成了 article 標籤。

最大原因是 一種視覺介面對應的實現方式太多,不僅標籤可以各異,css 屬性還有 table、block、flex、grid 可選,同時 grid 屬性還會導致視覺結構與 DOM 結構不完全對應。

如果你今天用 css 選擇器做了一套完全貼合現在 DOM 結構的 css 檔案,這個 css 檔案也許是後面 dom 結構改動的噩夢。

你敢做全域性樣式覆蓋嗎

我們排除標籤,僅對屬性做全域性覆蓋,的確可以部分繞開 DOM 結構的限制,但是這樣的全域性樣式覆蓋,不同的人有不同看法。

小明的團隊非常懂得 css 運用,他們每天都會花一個小時討論專案的 css 架構,並對通用需求樣式做了抽象,並且每個人都很認可這個方案,在他們的團隊,一個非常酷炫的按鈕與動畫效果,通過 <button animate /> 就可以完成,頁面間互動非常流暢,使用者體驗統一,前端程式碼也非常簡潔和優雅。

小白的團隊水平參差不齊,有人永遠只使用 table 佈局,有人卻總想將一些試驗階段 css 屬性用在生產環境,小白自己抽象了一個全域性樣式 css 檔案,可團隊沒什麼時間溝通,甚至有人私下也注入了不少全域性 css 樣式,總有人抱怨自己的樣式被全域性覆蓋了,最後小白甚至不得不在自己頁面入口處寫上 *: unset 清空各種奇怪的全域性樣式干擾,他想清空那該死的全域性 css 樣式檔案,但他知道這樣做帶來的是更大的災難。

可以看到,並不是每個團隊都適合做全域性樣式覆蓋。

JS 模組化思維的影響

為什麼一個專案安裝了幾百個 npm 三方包,卻依然可以正常執行?因為好的三方包都是遵守模組化的,同時也不產生副作用,這樣被使用時的效果就可以被預期,試想一下幾百個 npm 包裡同時定義了不同規範的全域性 css 覆蓋,你的專案會成為什麼樣。

當然 js 與 css 是不適合放在一起比較的,css 大多是業務級別的,也就是能寫 css 只有做業務的你,第三方包一般是不會提供 css 定義干擾你的專案的。

然而大部分 UI 元件庫是自帶樣式的,他們有自己的設計哲學,但為什麼現在你會反感,而當初使用 Bootstrap 不會?

使用 Bootstrap 的時代,Bootstrap 一般是作為專案第一個依賴安裝的,我們明確知道它會注入全域性樣式。我們會泡在他的官方文件目錄,一條條理解他做的全域性樣式規則,他提供的各種 class。

然而現在是一個 Css-in-js 的時代,或者至少是 css-in-npm 的時代,什麼都用 npm 裝,什麼都是模組化的,很多時候我們用一個 UI 元件僅僅是為了在某一處地方使用,而不想接受他帶來的全域性樣式汙染,視覺設計哲學,更不想看他的 css 文件。所以好的元件庫往往 css 使用的很收斂,儘量不要對使用者專案環境造成影響。

如果你專案的樣式已經被不得不安裝的第三方包全域性覆蓋得面目全非,每一次對全域性樣式修改都如履薄冰,可能你會比較反感 css 選擇器,你會推崇更安全的 css modules,或甚至是 css-in-js,讓每個元件的 className 都唯一,做到標籤粒度的隔離。

4 總結

筆者認為,在一個確定的環境中,比如一個元件,一個獨立負責的模組,是比較適合用 css 選擇器的,這樣可以讓樣式程式碼更易讀,DOM 結構更清爽。但請一定注意作用域,如果不是大家一起達成的共識,最好不要放到全域性樣式中。

就算專案的風格非常明確,a 標籤一定要用紅色,在把這條規則放到全域性樣式之前,請思考一下,這樣會不會破壞了某個用 a 標籤模擬按鈕的元件庫的樣式?

css 屬性選擇器的強大功能,需要有良好的專案管理做支撐,或者通過技術手段比如 shadow dom 做支撐。不過 shadow dom 的支援程度 現在仍然很低,所以使用編譯工具做的隔離,在某種程度上模擬了 Css 選擇器,承擔了 Css 選擇器 + shadow dom 的功能。

一切樣式都用 className 控制,也許是 shadow dom 出來前的一種妥協方案,這篇文章更多是在描述 Css 選擇器設計之美,但需要我們理性去使用。

討論地址是:精讀《使用 CSS 屬性選擇器》 · Issue #113 · dt-fe/weekly

如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

相關文章