在網頁切版時,熟練掌握 CSS ::before 與 ::after 偽元素用法,可以在不增加多餘 HTML 標籤(如
<span>或<i>)的前提下,為元素前後加上裝飾色條、小圖示、遮罩或引號。這些偽元素並非真實存在於 DOM 樹中的節點,而是瀏覽器在渲染階段(Render Tree)動態產生的虛擬子元素(底層渲染流程可參考 瀏覽器渲染機制解析:DOM Tree、Reflow 與 Repaint)。
一、 偽元素(::)與偽類(:)差別及常用偽元素對照表
初學者最常混淆的問題是:「為什麼有時候看到單冒號 :before,有時候看到雙冒號 ::before?偽元素跟 :hover 差在哪?」
Selectors Level 3 規範為了明確區分兩者,規定 偽類(Pseudo-classes)使用單冒號 :,而 偽元素(Pseudo-elements)使用雙冒號 ::(為了相容 CSS2,瀏覽器仍接受 :before、:after、:first-line、:first-letter 這 4 個寫單冒號,但後來新增的偽元素只能寫雙冒號,現代開發建議統一寫雙冒號):
| 分類與語法 | 作用原理 | 常見代表語法 |
|---|---|---|
偽元素(雙冒號 ::) | 在元素內部前後或特定部位虛擬創造出一個新元素 | ::before、::after、::placeholder、::selection(反白樣式,詳見 CSS 禁止選取)、::marker(清單項目符號與 <summary> 展開箭頭,詳見 CSS 去除 details 預設箭頭) |
偽類(單冒號 :) | 選取既有元素處於特定狀態或順序時的樣式(不產生新元素) | :hover、:focus(焦點外框,詳見 CSS 移除超連結藍色外框)、:first-child、:nth-child(n) |
偽元素與一般 DOM 節點(如 span 標籤)的差異
| 特性項目 | 一般 DOM 節點(如 <span>) | 偽元素(::before / ::after) |
|---|---|---|
| HTML 原始碼是否存在 | 是(存在於 DOM Tree 中) | 否(僅存在於 CSS 渲染樹 Render Tree 中) |
| 必須設定的屬性 | 無強制規定 | 必須設定 content 屬性(即使為空字串 content: "";;none、normal 或沒寫都不會產生) |
預設 display 型態 | inline | inline(若要設定 width/height 需改為 inline-block 或 block) |
| JS 直接綁定事件 | 可直接 querySelector 綁定 | 無法直接選取,需透過父元素或 CSS 變數控制 |
二、 核心用法拆解:文字前綴、裝飾色條與圖片大小控制
1. 基礎寫法:插入文字、符號與 attr() 動態屬性
::before 會在目標元素「內部內容的最前面」插入虛擬子元素;::after 則會在「內部內容的最後面」插入。除了直接寫死文字外,還可以用 content: attr(屬性名稱) 直接讀取 HTML 上的 data-* 屬性(關於 data-* 規範可參考 HTML Global Attributes 全域屬性):
/* 1. 在價格前方自動加上幣別符號 */
.price::before {
content: "NT$ ";
font-weight: bold;
}
/* 2. 在外部連結後方自動加上箭頭符號 */
.external-link::after {
content: " ↗";
font-size: 0.85em;
}
/* 3. 使用 attr() 讀取 HTML 的 data-tooltip 屬性製作提示框 */
.tooltip-btn {
position: relative;
}
.tooltip-btn:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
background: #1e293b;
color: #ffffff;
font-size: 12px;
white-space: nowrap;
border-radius: 4px;
}
要注意 content 產生的文字不在 DOM 中,部分螢幕閱讀器與瀏覽器的組合不會讀出來,所以價格、狀態這類理解頁面必需的資訊應該直接寫在 HTML 裡。content 也可以在值後面加上斜線與替代文字,提供給螢幕閱讀器使用;純裝飾的符號可以把替代文字設為空字串:
/* 箭頭只是裝飾:替代文字留空 */
.external-link::after {
content: " ↗" / "";
}
/* 圖示有意義:提供替代文字 */
.pdf-link::before {
content: url("/images/pdf.png") / "PDF 檔案:";
}
2. 為什麼 ::before 放圖片無法調整寬高?(正確控制圖片大小寫法)
許多人在 ::before 放小圖示時會寫 content: url("icon.png");,接著發現不管怎麼設 width 和 height,圖片大小都完全不會變!
這是因為 content: url(...) 插入的圖片是偽元素裡的匿名替換元素(anonymous replaced element),width 與 height 設定的是偽元素這個外框,而不是裡面的圖片,CSS 也沒有選擇器能直接選到這張圖片,所以圖片會維持原始尺寸顯示(就算把偽元素改成 inline-block,圖片也只會超出 16px 的外框)。若要自由控制偽元素的圖片大小,應將 content 設為空字串 "",並改用 background-image 搭配 background-size: contain:
/* ❌ 錯誤寫法:用 content: url() 無法調整圖片的 width 與 height */
.bad-icon::before {
content: url("/images/star.png");
width: 16px; /* 無效! */
height: 16px; /* 無效! */
}
/* ✅ 建議寫法:content 留空,改用 background-image 與 background-size 控制寬高 */
.good-icon::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
background-image: url("/images/star.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
}
3. 切版最常用:父元素 relative + ::before absolute 製作標題裝飾線與遮罩
當需要為標題左側加上一條垂直色塊,或在卡片上方覆蓋一層半透明遮罩時,只要將父元素設為 position: relative,再將 ::before 設為 position: absolute 即可精準定位:
.section-title {
position: relative;
padding-left: 14px;
}
.section-title::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 1.1em;
background-color: #2563eb;
border-radius: 2px;
}
三、 為什麼設定了 ::before / ::after 卻沒反應?(4 大無效原因排查)

當您寫了偽元素卻在畫面上完全看不到時,請依序檢查以下 4 個最常見的原因:
- 漏寫了
content屬性:只要沒有寫content(哪怕只是畫一個色塊或線條,也必須寫content: "";),瀏覽器就完全不會渲染該偽元素。 - 套用在
<img>、<input>、<br>、<iframe>等標籤上:::before與::after會被當成元素的第一個與最後一個子元素來排版,而<img>、<video>、<iframe>這類 替換元素(Replaced Elements) 的內容來自外部資源,不受 CSS 排版模型控制,因此不能使用::before與::after。<input>、<textarea>、<select>等表單控制項與<br>也不應該依賴偽元素。
瀏覽器之間有例外行為:例如 Chrome 會在核取方塊(type="checkbox")與圖片載入失敗的<img>上顯示偽元素,但這不是規範保證的行為,不要拿來當正式寫法。<hr>則不是替換元素,可以正常使用::before與::after。
解決方法:請在<input>或<img>外層包一個<div class="input-wrapper">,把::before/::after寫在外層的div上。 - 忘了改
display就直接設寬高:偽元素預設是display: inline(行內元素),若content: ""是空字串,又沒有設定display: block、inline-block或position: absolute,設定的width與height就會完全無效(寬高為0×0)。 - 設了
z-index: -1結果被父元素的背景色蓋住:若想把::before當成底圖放在父元素文字後方而設了z-index: -1,它會跑到父元素背景色底下而消失。解決方法是在父元素加上position: relative; z-index: 1;建立獨立的堆疊層級。
四、 JavaScript 如何讀取或動態修改偽元素?
由於偽元素不在 DOM 樹中,使用 document.querySelector('.box::before') 會回傳 null。若需透過 JavaScript 操作偽元素,可使用以下兩種方式:
1. 使用 CSS 變數(--var)動態修改偽元素內容或顏色
透過 JavaScript 修改父元素上的 CSS 變數(Custom Properties),偽元素即可即時響應變更:
.badge::after {
content: var(--badge-text, "NEW");
background-color: var(--badge-bg, #ef4444);
}
const badge = document.querySelector('.badge');
// 注意:content 變數值需包含內層引號
badge.style.setProperty('--badge-text', '"HOT"');
badge.style.setProperty('--badge-bg', '#f59e0b');
2. 使用 window.getComputedStyle 讀取偽元素的屬性值
window.getComputedStyle(element, pseudoElt) 的第二個參數可直接傳入 '::before' 或 '::after' 來讀取計算後的樣式:
const box = document.querySelector('.box');
const beforeContent = window.getComputedStyle(box, '::before').getPropertyValue('content');
console.log(beforeContent); // 例如 "\"NEW\"":字串值會連同引號一起回傳;沒有偽元素時為 "none"
五、 常見問題與排錯 Q&A
Q1:偽元素做成的遮罩擋住了下方按鈕的點擊事件怎麼辦?
- 問題原因:當
::after使用position: absolute覆蓋在父元素上方時,會攔截滑鼠點擊事件。 - 解決思路:在偽元素 CSS 中加上
pointer-events: none;,即可讓滑鼠點擊直接穿透偽元素觸發下方的按鈕或連結。
Q2:在 content 裡面要如何換行?
- 問題原因:在 CSS
content字串中直接寫<br>或\n預設不會換行(會把<br>當純文字印出)。 - 解決思路:在
content中使用換行轉義碼\A,並同時加上white-space: pre;(或pre-wrap,詳見 CSS 文字換行、單字換行、不換行):
.multiline::after { content: "第一行文字\A第二行文字"; white-space: pre; }
發佈留言