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 樹來計算,比一般選擇器更耗效能。幾點建議:
- 縮小選擇範圍:
section:has(img)比*:has(img)好 - 避免過度嵌套:
:has()內的選擇器不要太複雜 - 不要監聽高頻更新的屬性:如
: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() 吧!
分享這篇文章