跳至主要內容

CSS :has() 選擇器:改寫前端開發規則的父元素選擇器

5 分鐘閱讀 950 字

CSS :has() 選擇器:改寫前端開發規則的父元素選擇器

多年來,CSS 一直缺少一個功能:父元素選擇器。我們可以選「某個元素的子元素」,卻無法選「包含某個子元素的父元素」。這個限制讓許多視覺效果不得不依賴 JavaScript 來實現。

直到 :has() 偽類選擇器出現,一切都改變了。

瀏覽器支援現況

截至 2025 年底,:has() 已獲得所有主流瀏覽器的支援:

  • Chrome 105+
  • Firefox 121+
  • Safari 15.4+
  • Edge 105+

全球支援率超過 92%,已可安全用於生產環境。

基本語法

/* 選取「包含 img 的 figure 元素」 */
figure:has(img) {
  border: 2px solid #eee;
  border-radius: 8px;
}

/* 選取「包含已勾選 checkbox 的 label 元素」 */
label:has(input[type="checkbox"]:checked) {
  font-weight: bold;
  color: #2563eb;
}

實際應用場景

場景一:表單驗證視覺回饋

以往需要 JavaScript 監聽 input 事件才能更改父元素樣式,現在純 CSS 就能做到:

/* 當 input 有值時,讓 label 浮起 */
.form-group:has(input:not(:placeholder-shown)) label {
  transform: translateY(-1.5rem);
  font-size: 0.75rem;
  color: #6366f1;
}

/* 當 input 不合法時,整個 form-group 變紅 */
.form-group:has(input:invalid:not(:placeholder-shown)) {
  --border-color: #ef4444;
}

.form-group:has(input:invalid:not(:placeholder-shown)) label {
  color: #ef4444;
}

場景二:智慧型格線佈局

根據子元素的數量自動調整格線欄數:

.grid {
  display: grid;
  gap: 1rem;
}

/* 1 個子元素 */
.grid:has(> :nth-child(1):last-child) {
  grid-template-columns: 1fr;
}

/* 2 個子元素 */
.grid:has(> :nth-child(2):last-child) {
  grid-template-columns: repeat(2, 1fr);
}

/* 3 個或更多子元素 */
.grid:has(> :nth-child(3)) {
  grid-template-columns: repeat(3, 1fr);
}

場景三:導覽列狀態

/* 當導覽列有下拉選單展開時,加深背景 */
nav:has(.dropdown:hover) {
  background-color: rgba(0, 0, 0, 0.9);
}

/* 當行動選單開啟時,防止頁面捲動 */
body:has(.mobile-menu.open) {
  overflow: hidden;
}

場景四:條件式圖片樣式

/* 包含 caption 的圖片容器 */
figure:has(figcaption) img {
  border-radius: 8px 8px 0 0;
}

/* 不包含 caption 的圖片 */
figure:not(:has(figcaption)) img {
  border-radius: 8px;
}

場景五:深色模式切換

搭配 checkbox 做純 CSS 深色模式切換器:

/* 當 #dark-toggle 被勾選時,切換整頁深色模式 */
body:has(#dark-toggle:checked) {
  background-color: #1a1a2e;
  color: #e0e0e0;
}

body:has(#dark-toggle:checked) .card {
  background-color: #16213e;
  border-color: #0f3460;
}
<input type="checkbox" id="dark-toggle" hidden>
<label for="dark-toggle" class="theme-toggle">🌙 深色模式</label>

進階技巧:邏輯組合

:has() 可以和其他偽類組合使用:

/* 包含 .error 但不包含 .success 的容器 */
.notification:has(.error):not(:has(.success)) {
  border-left: 4px solid red;
}

/* 包含兩種特定子元素 */
.article:has(h2):has(img) {
  display: grid;
  grid-template-columns: 1fr 300px;
}

/* 選取「前一個同層元素包含 .active 的元素」 */
/* 這等於實現了某種「前一個相鄰選擇器」 */
.item:has(+ .item .active) {
  opacity: 0.5;
}

效能注意事項

:has()關係型偽類,瀏覽器需要掃描 DOM 樹來計算,比一般選擇器更耗效能。幾點建議:

  1. 縮小選擇範圍section:has(img)*:has(img)
  2. 避免過度嵌套:has() 內的選擇器不要太複雜
  3. 不要監聽高頻更新的屬性:如 :has(input:focus) 在大型表單中要小心

:has() 與 JavaScript 的比較

以前需要這樣做:

document.querySelectorAll('label').forEach(label => {
  const checkbox = label.querySelector('input[type="checkbox"]');
  if (checkbox) {
    checkbox.addEventListener('change', () => {
      label.classList.toggle('checked', checkbox.checked);
    });
  }
});

現在只需要:

label:has(input[type="checkbox"]:checked) {
  /* 你的樣式 */
}

降級策略

對於不支援的舊瀏覽器,可以使用 @supports 做降級:

/* 降級方案 */
.form-group.has-error {
  border-color: red;
}

/* 支援 :has() 的瀏覽器使用純 CSS 方案 */
@supports selector(:has(*)) {
  .form-group:has(input:invalid:not(:placeholder-shown)) {
    border-color: red;
  }
}

小結

:has() 是近年來 CSS 最重要的新功能之一,它不只是父元素選擇器,更是一個強大的關係型查詢工具。許多過去必須靠 JavaScript 實現的視覺邏輯,現在可以完全移至 CSS 層處理,減少 JavaScript 的負擔,讓關注點更加分離。開始在你的專案中使用 :has() 吧!

分享這篇文章