iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Modern Web

前端迷走中:從零開始的新手之路系列 第 23

[Day 23] 選擇不障礙,讓CSS幫你──偽元素選擇器

  • 分享至 

  • xImage
  •  

前兩天我們介紹了偽類選擇器。

今天我們要介紹類似的偽元素選擇器。

偽元素簡介

定義

偽元素選擇器(Pseudo-Elements),也可以譯作虛擬元素選擇器。從字義來看,是一種依據實際上不存在的元素來挑選內容的選擇器。

跟偽類類似,可以將偽元素理解成系統依我們的指令,在文件檔案加上虛擬標籤,建立了可以透過選擇器選取並設定樣式的虛擬元素。

這些虛擬標籤並不會真的出現在文件的檔案中,也不會影響到DOM樹狀模型,所以稱這些標籤組成的元素是實際上不存在的偽元素。

有了這些偽元素,可以不受文件檔案限制,對原先因為沒有對應元素存在而無法以選擇器選取的特定內容設定樣式。

像是::first-line::first-letter,可以分別選取到元素第一行跟第一個字母的內容。哪些內容是元素的第一行跟第一個字母,無法直接從檔案本身判斷,會因為佈局的變化而動態調整。

透過偽元素,甚至也能在文件中加入原先不存在於檔案中的內容並設定樣式。例如::before::after,可以分別在元素的前與後插入指定內容並設定樣式。

語法

偽元素選擇器的語法為::name

偽元素名稱以兩個冒號::開頭,跟偽類以冒號開頭類似。這是因為早期的規範(CSS Level 1跟CSS Level 2)沒有區分兩者的語法。

為了相容於早期使用單冒號的網頁,瀏覽器仍然支援單冒號的偽元素語法,不過只限於當時就有的四種偽元素,包括::first-line::first-letter::before::after。後續定義的其他偽元素只能使用兩個冒號來表示。

為免之後不再支援單冒號的寫法,建議還是都使用兩個冒號的語法來表示偽元素。

兩個冒號間不能有空格;冒號跟偽元素名稱之間也不能有空格。
偽元素名稱同樣不區分大小寫。但還是建議都用小寫,比較有一致性。

注意事項

依附於元素存在

偽元素無法獨立存在,需要依附於另一個元素。這個元素稱作起源元素(originating element)。

因此使用偽元素選擇器時,通常都會在冒號前面加上元素選擇器,或是代表起源元素的複合選擇器,組成一個複合選擇器。像是element::name.class::name(等同於*.class::name);
如果沒有加上元素選擇器或複合選擇器,則是依附到所有合適的元素上,等同於*::name,只是把前面的通用選擇器省略掉。

與其他選擇器的搭配使用

偽元素選擇器跟前面提到的元素選擇器、類別選擇器、ID選擇器、通用選擇器、屬性選擇器還有偽類選擇器不同,不是規範定義的簡單選擇器(simple selector)。[1]

原則上,偽元素選擇器後面不能接上其他選擇器組成複合選擇器,或是後面接上組合器與其他選擇器組成複雜選擇器,除非規範有規定。

目前Selectors Level 4只有規定偽元素選擇器可以跟使用者操作偽類組合成複合選擇器,但只限於有明確提到可以用在偽元素的偽類,像是:hover。其他沒有提到的偽類,像是focus,就不能跟偽元素組合在一起。[1]

如果偽元素後面有其他選擇器,則這些選擇器設定的樣式只會套用到偽元素本身,不會影響到整個起源元素。

偽元素的偽元素

基本上,偽元素不能與其他偽元素相接組成複合選擇器,除非規範有明確規定。

所以一個複合選擇器內,通常只能有一個偽元素存在,並放在最後面。

Selectors Level 4有提到一些偽元素彼此可以相接組成複合選擇器。像是當::before被設定以list-item顯示時,後面可以接上::marker,變成::before::marker。

在這種由多個偽元素組成複合選擇器的情況下,前面的偽元素會是後面偽元素的起源元素,而後面的偽元素會是前面偽元素的子偽元素(sub-pseudo-elements)。

如果往前追溯,這群偽元素最終所依附的真實元素,則稱作最終起源元素(ultimate originating element)。

::first-line 選出元素的第一行字

如果想要對段落的第一行字設定特別的樣式,需要先將第一行字添加<span>再以選擇器設定樣式。

但這不是合適的做法,因為第一行字由哪些文字組成是不固定的,會因為螢幕框度、版面佈局、字體大小等因素影響。

這種時候,就需要改用::first-line

偽元素選擇器::first-line,代表起源元素的第一行字。會隨著網頁渲染結果的變化動態調整,自動選出段落的第一行。

虛擬標籤

使用::first-line時,就像是瀏覽器加上虛擬標籤,將元素的第一行字包起來。

比如說,以下出自規範的例子:

<P>This is a somewhat long HTML
paragraph that will be broken into several
lines. The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>

加上虛擬標籤後可能就會變成:

<P><P::first-line> This is a somewhat long HTML
paragraph that </P::first-line> will be broken into several
lines. The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>

於是可以透過規則將第一行文字變成大寫:

p::first-line { text-transform: uppercase }

起源元素內有其他元素

後代元素為行內元素

有時候,可能會將某些文字加上<a>變成超連結,或是添加<span>以設定樣式。

如果這些文字目前剛好橫跨第一行跟其他行,::first-line就會把這些元素切斷。

像是以下出自規範的例子,第一行原本以<span>元素開頭:

<P><SPAN class="test"> This is a somewhat long HTML
paragraph that will be broken into several
lines.</SPAN> The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>

加上虛擬標籤時,會在截斷的地方補上標籤,並以虛擬標籤將前半段的<span>元素包起來。於是原本的<span>元素就分成兩半了。

<P><P::first-line><SPAN class="test"> This is a
somewhat long HTML
paragraph that will </SPAN></P::first-line><SPAN class="test"> be
broken into several
lines.</SPAN> The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>

這種情況下,前半段的<span>因為包在虛擬標籤<P::first-line>內,會繼承而套用p::first-line設定的樣式;
後半段的<span>因為不是偽元素的子元素,就不會繼承而套用樣式。

後代元素為區塊元素

如果起源元素內還有其他區塊層級的後代元素,且後代元素仍然處在同一個flow裡面(沒有設定為floating或positioning),則起源元素透過::first-line選出的第一行,可能會出現在後代元素裡。

像是以下出自規範的例子,起源元素<div>裡面還有兩個<p>元素。

<DIV>
  <P>First paragraph</P>
  <P>Second paragraph</P>
</DIV>

加上虛擬標籤後,虛擬標籤會加到兩個最內層的<p>元素裡面。以<p>元素的第一行作為起源元素的第一行:

<DIV>
  <P><DIV::first-line><P::first-line>First paragraph</P::first-line></DIV::first-line></P>
  <P><P::first-line>Second paragraph</P::first-line></P>
</DIV>

::first-line的樣式

::first-line類似於行內層級元素。

::first-line能設定的樣式有限,規範說可以設定的屬性有下列這些。但瀏覽器等使用者代理程式也可能會套用其他樣式:[2]

  • font properties
  • color property
  • background properties
  • ‘word-spacing’
  • ‘letter-spacing’
  • ‘text-decoration’
  • ‘text-transform’
  • ‘line-height’

注意事項

使用::first-line時,需要注意:[2]

  • ::first-line只能用在區塊層級的元素,像是block box、 inline-block、table-caption、table-cell等。
  • 如果元素的第一行字在後代元素裡,且後代元素是inline-block或table-cell,p::first-line會無效。
  • 如果元素在第一行就換行,p::first-line會無效。

::first-letter 選出元素的第一個字母

除了第一行,也可以用::first-letter選出第一個字。

通常會用於設定首字大寫(initial caps),或首字下沉(drop caps)的樣式。

第一個字的組成

第一個字可以是字母,也可以是數字或符號。

如果第一個字前後有標點符號的話,這些標點符號也會包括在內。

有些語言可能會同時以多個字母組成一個單元。所以由::first-letter挑選的第一個字,可能會同時有多個字母。

不過如果這些標點符號或單元內的多個字母不在同一個元素裡,::first-letter會挑選出什麼則不一定。

像是<p>‘<em>T…的情況,可能只有‘,可能是‘T,也可能什麼也沒有。

起源元素內有其他元素

如果起源元素內還有後代元素,且後代元素仍然處在同一個flow裡面,則起源元素透過::first-line選出的第一行,可能會出現在後代元素裡。

後代元素為行內元素

如果文字有加上超連結,或是添加<span>,則::first-letter會在這些元素裡面添加虛擬標籤,跟::first-line不同。

::first-line會把這些元素分成兩半,並將前半段包在虛擬標籤裡面。

像是以下出自規範的例子,<P><span>元素開頭:

<P><SPAN>The first</SPAN> few words of an article
    in The Economist.</P>

加上虛擬標籤時,是在<span>元素裡面加上虛擬標籤。

<P>
<SPAN>
<P::first-letter>
T
</P::first-letter>he first
</SPAN>
few words of an article in the Economist.
</P>

後代元素為區塊元素

以下出自規範的例子,起源元素<div>裡有一個<p>元素。

<div>
<p>The first text.

加上虛擬標籤後,虛擬標籤會加到內層的<p>元素裡面。以<p>元素的第一個字作為起源元素的第一個字:

<div>
<p><div::first-letter><p::first-letter>T</...></...>he first text.

::first-letter的樣式

如果float屬性設為none::first-letter類似於行內層級元素;
如果是設定為其他值,則類似漂浮元素floated element。

::first-letter能設定的樣式同樣有限,規範說可以設定的屬性有下列這些。但瀏覽器等使用者代理程式也可能會套用其他樣式:[2]

  • font properties
  • ‘text-decoration’
  • ‘text-transform’
  • ‘letter-spacing’
  • ‘word-spacing’ (when appropriate)
  • ‘line-height’
  • ‘float’
  • ‘vertical-align’ (only if ‘float’ is ‘none’)
  • margin properties
  • padding properties
  • border properties
  • color property
  • background properties.

跟其他普通元素不同,為了正確呈現首字大寫或首字下沉的效果,瀏覽器等使用者代理程式可能會根據字母的形狀,選擇適當的行高、寬度、高度。

與其他偽元素的搭配使用

搭配::first-line

::first-line設定第一行的樣式時,可以同時使用::first-letter對第一個字設定其他的樣式。

像是以下出自規範的例子:

<P>Some text that ends up on two lines</P>

同時以::first-line::first-letter設定樣式時,會將::first-letter的虛擬標籤,加在::first-line的虛擬標籤裡:

<P>
<P::first-line>
<P::first-letter>
S
</P::first-letter>ome text that
</P::first-line>
ends up on two lines

由於::first-letter::first-line裡面,第一個字S會繼承對第一行設定的樣式;
不過如果第一個字S以設定相同屬性的樣式,則會蓋過對第一行的樣式。

搭配::before::after

如果使用::before::after,在元素前或後生成內容,也可以同時使用::first-letter對這些內容的第一個字設定其他的樣式。

比如以p::before {content: "Note: “}生成內容,可以使用p::first-letter對Note的N設定樣式。

注意事項

使用::first-letter時,需要注意:[2]

  • ::first-letter只能用在區塊層級的元素,像是block box、 inline-block、list-item、table-caption、table-cell等。
  • 如果元素的第一個字前有其他內容,像是行內表格或圖片,::first-letter會無效。
  • 如果元素的第一個字不在第一行的開頭,::first-letter會無效。
    像是第一行就換行,或文件為雙向文本(bidirectional reordering)。
  • 如果元素的第一個字在後代元素裡,且後代元素是inline-block或table-cell,::first-letter會無效。
  • 如果元素是清單項目(display設為list-item),且list-style-position設定為inside::first-letter可能會無效。

::before::after 在元素的前、後新增內容

如果要在元素前插入內容,可以使用::before
如果要在元素後插入內容,可以使用::after

使用::before::after時需要搭配content屬性,以content屬性設定要新增什麼內容。

例如以下由規範提供的例子便透過::before,在class屬性值為note<p>元素前,插入字串Note:

p.note:before { content: "Note: " }

透過::before::after新增的內容,包含在起始元素產生的Box裡面;
這些內容會繼承起始元素的樣式,如果是非繼承的屬性則會設為起始值。[3]

除了字串,content屬性還可以插入圖片、引文引號;對標題或有序清單設定編號種類;對無序清單設定項目標記的樣式。[3]

搭配::first-line::first-letter

針對::before::after新增的內容,可以搭配使用::first-line::first-letter,設定第一行字或第一個字的樣式。

例如以下由規範提供的例子,先透過::before,在class屬性值為special<p>元素前,插入字串Special! ;接著透過::first-letter,設定字串第一個字S的顏色。

p.special:before {content: "Special! "}
p.special:first-letter {color: #ffd800}

小結

今天我們介紹了偽元素的定義,以及四種偽元素。

包括表示元素第一行字的::first-line
表示元素第一個字的::first-letter
可以在元素前新增內容的::before
可以在元素後新增內容的::after

接下來要討論的是,當不同種類的選擇器對同個屬性設定樣式時,選擇器之間的優先順序。

參考資料

[1]: W3C, Selectors Level 4
[2]: W3C, Selectors Level 3
[3]: W3C, Cascading Style Sheets Level 2 Revision 1 (CSS 2.1), Generated content, automatic numbering, and lists


上一篇
[Day 22] 選擇不障礙,讓CSS幫你──偽類選擇器 下
系列文
前端迷走中:從零開始的新手之路23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言