關於兩者的區別,其實是很古老的問題。但是時至今日,由於各種網路誤傳以及一些不負責任的書籍誤筆,仍然有相當多的人將偽類與虛擬元素混為一談,甚至不乏很多CSS老手。早些年剛入行的時候,我自己也被深深誤導,因為論壇裡的文章大多不關心這種概念的細微差別,即使有人出來說一句:“這兩個是不同的”,也只是被更多的文章淹沒掉而已。所以覺得有必要寫下這些我所知的部分,這裡著重寫的是為什麼這兩者不同,以及一些平時容易錯過的細節。
無論是偽類還是虛擬元素,都屬於CSS選取器的範疇。所以它們的定義可以在CSS標準的選取器章節找到。分別是 CSS2.1 Selectors 和 CSS Selector Level 3,兩者都已經是推薦標準。
標準的定義
在CSS2.1裡,5.10 Pseudo-elements and pseudo-classes 描述了這兩個概念的由來,它們是被一同提及的。但到了 Selector Level 3 裡,它們就被分開到兩個小節裡加以區分。但無論如何,偽類和虛擬元素的引入都是因為在文檔樹裡有些資訊無法被充分描述,比如CSS沒有“段落的第一行”之類的選取器,而這在一些出版情境裡又是必須的。用標準裡的話說:
CSS introduces the concepts of pseudo-elements and pseudo-classes to permit formatting based on information that lies outside the document tree.
簡單翻譯一下,就是:
CSS 引入偽類和虛擬元素的概念是為了實現基於文檔樹之外的資訊的格式化
這麼說很抽象,其實就是為了描述一些現有CSS無法描述的東西。缺少什麼,則引入什麼,不管是標準,還是人,都是如此成長而來。
偽類與虛擬元素的區別
這裡我大可以列一個表格,把所有的偽類和虛擬元素分開羅列,但這未免太形式化,與其記住“哪些是哪些不是”,不如真正地加以區分。偽類和虛擬元素本身就有一個根本的不同之處,這點直接體現在了標準的描述語句上。
先看一個虛擬元素 first-line
例子。現在有一段HTML,內容是一個段落:
1234 |
<p>I am the bone of my sword. Steel is my body, and fire is my blood. I have created over a thoustand blades. Unknown to Death.Nor known to Life. Have withstood pain to create many weapon. Yet, those hands will never hold anything. So as I pray, unlimited blade works.</p> |
如果我要描述這個段落的第一行,在不用虛擬元素的情況下,我會怎麼做?想來我一定要嵌套一層 span
,然後加上類名:
1234 |
<p><span class="first-line">I am the bone of my sword. Steel is my body, and fire is my blood. </span> I have created over a thoustand blades.Unknown to Death.Nor known to Life. Have withstood pain to create many weapon. Yet, those hands will never hold anything. So as I pray, unlimited blade works.</p> |
再反觀一個偽類 first-child
的例子,有一個簡單的列表:
1234 |
<ul><li></li><li></li></ul> |
如果我要描述 ul
的第一個元素,我無須嵌套新的元素,我只須給第一個已經存在的 li
添加一個類名就可以了:
1234 |
<ul><li class="first-child"></li><li></li></ul> |
儘管,第一行和第一個元素,這兩者的語意相似,但最後作用的效果卻完全不同。所以,偽類和虛擬元素的根本區別在於:它們是否創造了新的元素(抽象)。從我們模仿其意義的角度來看,如果需要添加新元素加以標識的,就是虛擬元素,反之,如果只需要在既有元素上添加類別的,就是偽類。而這也是為什麼,標準精確地使用 “create” 一詞來解釋虛擬元素,而使用 “classify” 一詞來解釋偽類的原因。一個描述的是新建立出來的“幽靈”元素,另一個則是描述已經存在的符合“幽靈”類別的元素。
偽類一開始單單只是用來表示一些元素的動態狀態,典型的就是連結的各個狀態(LVHA)。隨後CSS2標準擴充了其概念範圍,使其成為了所有邏輯上存在但在文檔樹中卻無須標識的“幽靈”分類。
虛擬元素則代表了某個元素的子項目,這個子項目雖然在邏輯上存在,但卻並不實際存在於文檔樹中。
偽類和虛擬元素混淆的由來
最為混淆的,可能是大部分人都將 :before
和 :after
這樣的虛擬元素隨口叫做偽類,而且即使在概念混淆的情況下,實際使用上也毫無問題——因為即使概念混淆,對真正使用也不會造成多少麻煩:)
CSS Selector Level 3 為了區分這兩者的混淆,而特意用冒號加以區分:
- 偽類用一個冒號表示
:first-child
- 虛擬元素則使用兩個冒號表示
::first-line
並且規定,瀏覽器既要相容CSS1和2裡既存的虛擬元素的單冒號表示,同時又要不相容CSS3新引入的虛擬元素的單冒號表示。後來的結果大家都知道,因為低版本IE對雙冒號的相容問題,幾乎所有的CSSer在寫樣式的時候都不約而同的使用了單冒號。這無形中,讓這種混淆延續了下來。而CSS3新虛擬元素的使用到目前為止,還遠遠不成氣候。
偽類和虛擬元素使用上需要注意的地方
明白其不同之後,就需要注意和考慮在實際使用上的一些問題。比如:偽類和虛擬元素的選取器特殊性(優先順序)如何計算?
我在之前的 CSS選取器距離無關 一文中,翻譯過CSS標準的計算選取器的特殊性這一部分,看完那部分,答案就清楚了:除了否定偽類的特殊規定外,分開各自作為真正的類和元素計算。
雖然標準以後的版本可能會允許選取器多虛擬元素的情況,但就目前為止,虛擬元素在一個選取器裡只能出現一次,並且只能出現在末尾。實則,虛擬元素是選中了某個元素的符合邏輯的某個實際卻不存在的部分,所以應用中也不會有人將其誤寫成多個。偽類則是像真正的類一樣發揮著類的作用,沒有數量上的限制,只要不是相互排斥的偽類,也可以同時使用在相同的元素上。關於CSS3選取器的詳細解釋,推薦 rogerjohansson 的 CSS 3 selectors explained。
結束語
本來只是想稍稍寫點,不想話又多了…到了最後,我一度覺得自己還漏了很多,不斷在腦海裡搜尋,但可能只能下次在補充了。寫這篇的目的是為下篇《CSS偽類與CSS虛擬元素的典型應用》做個鋪墊,不想理論的東西一寫自己就開始廢話連篇了,慚愧…回看本篇,自己的思路跳的有些亂了,洋洋洒洒這麼多字,可能概括起來沒幾句話,但如果希望儘可能表達清楚,則又免不了冗餘過頭。理論總是顯得枯燥了些,下篇閑談應用應該不至於這麼沉悶:)
參考資料
- CSS2.1 Pseudo-elements and pseudo-classes from W3C
- Selectors Level 3 W3C Recommendation 29 September 2011 from W3C
- Difference between a pseudo-class and a pseudo-element by Laura L. Carlson
- THE DIFFERENCE BETWEEN PSEUDO-CLASSES AND PSEUDO-ELEMENTS by Stephan Muller
-----------------原文地址---------------------
CSS偽類與CSS虛擬元素的區別及由來