紀錄工作經驗、相關知識,解決技術相關問題。

CSS, HTML, 新手 Jamp Holes, 網站相關

CSS ::before 與 ::after 偽元素用法:content 屬性、圖片大小與無效原因

CSS ::before 與 ::after 偽元素用法:content 屬性、圖片大小與無效原因 特色圖片

在網頁切版時,熟練掌握 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 型態inlineinline(若要設定 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 大無效原因排查)

三、 為什麼設定了 ::before / ::after 卻沒反應?(4 大無效原因排查)

當您寫了偽元素卻在畫面上完全看不到時,請依序檢查以下 4 個最常見的原因:

  1. 漏寫了 content 屬性:只要沒有寫 content(哪怕只是畫一個色塊或線條,也必須寫 content: "";),瀏覽器就完全不會渲染該偽元素。
  2. 套用在 <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 上。
  3. 忘了改 display 就直接設寬高:偽元素預設是 display: inline(行內元素),若 content: "" 是空字串,又沒有設定 display: block、inline-block 或 position: absolute,設定的 width 與 height 就會完全無效(寬高為 0×0)。
  4. 設了 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; }


參考資料與延伸閱讀

發佈留言