CSS :has() 選擇器實戰:父選擇器改變元件樣式架構
前端工程(更新於 2026年6月9日)
:has() 選擇器:CSS 的「父選擇器」
長期以來,CSS 只能從父到子選擇元素。:has() 打破了這個限制,實現了從子到父的反向選擇:
/* 傳統:只能選子元素 */
.card .title { color: blue; }
/* :has():根據子元素狀態選父元素 */
.card:has(.title) { border-color: blue; }
瀏覽器支援
| 瀏覽器 | 支援版本 | 發布時間 |
|---|---|---|
| Chrome | 105+ | 2022.08 |
| Safari | 15.4+ | 2022.03 |
| Firefox | 121+ | 2023.12 |
| Edge | 105+ | 2022.08 |
截至 2026 年,所有主流瀏覽器均已支援
:has()。
語法與匹配邏輯
基本語法
/* 包含指定後代 */
parent:has(descendant) { }
/* 包含直接子元素 */
parent:has(> child) { }
/* 兄弟元素存在 */
element:has(+ sibling) { }
element:has(~ sibling) { }
/* 多條件組合 */
element:has(.a, .b) { } /* 或 */
element:has(.a):has(.b) { } /* 且 */
與 :not() 組合
/* 不包含 .active 子元素的 .nav */
.nav:not(:has(.active)) {
opacity: 0.5;
}
/* 包含未選中的 checkbox */
.form:has(input:not(:checked)) {
border-color: orange;
}
實戰案例 1:表單驗證狀態
傳統 JS 方案
// 需要監聽每個輸入框的驗證狀態
input.addEventListener('invalid', () => {
form.classList.add('has-error');
});
input.addEventListener('valid', () => {
form.classList.remove('has-error');
});
:has() 方案
/* 表單包含無效輸入時,整體樣式變化 */
form:has(:user-invalid) {
border-color: #ef4444;
}
form:has(:user-invalid) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
/* 單個欄位無效時,其容器高亮 */
.field:has(:user-invalid) {
background: #fef2f2;
}
.field:has(:user-invalid) .error-msg {
display: block;
}
/* 全部有效時顯示成功提示 */
form:has(.field:not(:has(:user-invalid))) .success-msg {
display: block;
}
<form>
<div class="field">
<label>電子郵件</label>
<input type="email" required />
<span class="error-msg">請輸入有效電子郵件</span>
</div>
<div class="field">
<label>密碼</label>
<input type="password" required minlength="8" />
<span class="error-msg">密碼至少 8 位</span>
</div>
<button class="submit-btn">提交</button>
<span class="success-msg">表單有效</span>
</form>
實戰案例 2:卡片版面配置
含圖片與無圖片的卡片自適應
/* 有圖片的卡片:水平版面配置 */
.card:has(img) {
display: flex;
gap: 1rem;
}
.card:has(img) .card-body {
flex: 1;
}
/* 無圖片的卡片:垂直版面配置 */
.card:not(:has(img)) {
display: block;
text-align: center;
}
/* 圖片為橫圖時調整比例 */
.card:has(img[width="1200"]) {
flex-direction: column;
}
卡片懸停效果
/* 滑鼠懸停在卡片任意位置,圖片縮放 */
.card:has(.card-link:hover) img {
transform: scale(1.05);
}
.card:has(.card-link:hover) .card-title {
color: var(--primary);
}
實戰案例 3:導覽高亮
傳統方案:伺服器端渲染時加入 active class
<nav>
<a href="/" class="active">首頁</a>
<a href="/blog">部落格</a>
</nav>
:has() 方案:純 CSS 感知當前頁面
/* 利用 aria-current 屬性 */
nav:has([aria-current="page"]) {
border-bottom-color: var(--primary);
}
nav a:has(+ [aria-current="page"]),
nav a[aria-current="page"] {
color: var(--primary);
font-weight: 600;
}
/* 導覽中有啟用項時,顯示回上一頁按鈕 */
nav:not(:has([aria-current="page"])) .back-btn {
display: none;
}
側邊欄折疊感知
/* 側邊欄有展開的子選單時,顯示分隔線 */
.sidebar:has(.submenu:not([hidden])) {
border-right: 2px solid var(--border);
}
/* 所有子選單都折疊時,緊湊模式 */
.sidebar:not(:has(.submenu:not([hidden]))) {
width: 64px;
}
實戰案例 4:暗色模式聯動
根據圖片亮度自動調整
/* 當頁面包含高亮度圖片時,周圍文字加深 */
figure:has(img.light) {
background: #1a1a1a;
color: #fff;
}
/* 暗色模式下,包含透明圖片的容器加背景 */
@media (prefers-color-scheme: dark) {
.container:has(img[alt*="logo"]) {
background: #fff;
padding: 8px;
border-radius: 8px;
}
}
實戰案例 5:表格與列表互動
表格全選感知
/* 所有 checkbox 都選中時,表頭樣式變化 */
table:has(tbody input:checked:not(:indeterminate):not(:only-child))
th {
background: var(--primary-light);
}
/* 有部分選中時,顯示批次操作欄 */
table:has(tbody input:checked) .batch-actions {
display: flex;
}
/* 無選中時隱藏 */
table:not(:has(tbody input:checked)) .batch-actions {
display: none;
}
列表空狀態
/* 列表為空時顯示空狀態提示 */
.list:has(> :only-child.empty-placeholder) {
justify-content: center;
min-height: 200px;
}
:has() 與 JavaScript 的取捨
| 場景 | :has() 優勢 | JS 優勢 |
|---|---|---|
| DOM 結構變化 | 自動回應,無需監聽 | 可處理非同步資料 |
| 效能 | 瀏覽器原生,樣式計算快 | 可精細控制計算時機 |
| 複雜邏輯 | 簡單條件判斷 | 多步計算、API 呼叫 |
| 相容性 | 2023 年後全支援 | 無相容問題 |
效能注意事項
/* 差:深層嵌套的 :has() 可能影響效能 */
.container:has(.deep1:has(.deep2:has(.deep3))) { }
/* 好:扁平的 :has() 條件 */
.container:has(.deep3) { }
避免多層嵌套
:has(),瀏覽器需要遍歷 DOM 樹來匹配,嵌套越深開銷越大。
與其他 CSS4 特性的配合
:is() 和 :where() 簡化選擇器
/* 使用 :is() 簡化多型別判斷 */
.card:has(:is(img, video, svg)) {
aspect-ratio: 16/9;
}
/* :where() 降低優先級,方便覆蓋 */
.card:where(:has(.featured)) {
border-color: gold;
}
:modal 和 :popover
/* 頁面有開啟的強制回應對話方塊時,禁止背景捲動 */
body:has(:modal) {
overflow: hidden;
}
body:has([popover]:not(:popover-closed)) {
overflow: hidden;
}
最佳實務總結
- 優先用 :has() 替代 JS class 切換:減少 JS 程式碼,樣式邏輯內聚
- 保持 :has() 條件扁平:避免多層嵌套影響效能
- 與語意屬性配合:
aria-current、:user-invalid、:checked等 - 漸進增強:核心功能不依賴 :has(),:has() 作為增強
- 配合 DevTools 除錯:Chrome DevTools 已支援 :has() 選擇器高亮
總結
:has() 選擇器是 CSS 發展史上最重要的能力擴展之一,它讓 CSS 具備了從子到父的反向選擇能力,大幅減少了表單驗證、條件樣式、狀態聯動等場景的 JavaScript 程式碼。掌握 :has() 的匹配邏輯和效能邊界,是現代前端工程師的必備技能。
使用 Flexbox 工具 練習版面配置組合,使用 Box Shadow 產生器 建立卡片陰影效果,使用 Border Radius 工具 設計卡片圓角。
本站提供瀏覽器本地工具,免註冊即可試用 →
#:has()#CSS选择器#父选择器#条件样式#CSS4