自從我們開始 (CSS 術語) 起,我們一直與串聯式各種情境合作。我們的樣式組成了「階層式樣式表」。我們的選取器也會串聯起來。讓它們傾斜。在大多數情況下,按鈕高度會下降。但從來不會向上。多年來,我們打造了「家長選取器」這個概念。現在終於到了!在 :has()
虛擬選取器的形狀中。
如果做為參數傳遞的任何選取器符合至少一個元素,:has()
CSS 虛擬類別代表元素。
但這其實不是「父項」選取器,這就是 Google Play 遊戲的行銷技巧最吸引人的方式可能是「條件環境」選取器,但那個圓環聽起來不太一樣。要如何使用「闔家適用」選取器?
瀏覽器支援
在深入說明之前,我想先介紹瀏覽器支援功能。它還不算什麼。但我現在就快完成了。目前尚不支援任何 Firefox,這會納入我們的發展藍圖。不過,目前已經在 Safari 中,並將於 Chromium 105 版本到期。本文中的所有示範模式都能讓您瞭解,如果所使用的瀏覽器不支援這些示範,
如何使用 :has
但究竟什麼是創新文化?」假設以下 HTML 含有兩個包含 everybody
類別的同層級元素。您會如何選取含有 a-good-time
類別的子系?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
您可以透過 :has()
搭配下列 CSS 達到這個目的。
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
這會選取第一個 .everybody
例項並套用 animation
。
在這個範例中,含有 everybody
類別的元素是目標。條件具有含有 a-good-time
類別的子系。
<target>:has(<condition>) { <styles> }
不過,由於 :has()
會帶來許多商機,因此您可以做得更遠。甚至是可能尚未發現的東西。請參考其中幾項。
選取有直接 figcaption
的 figure
元素。css
figure:has(> figcaption) { ... }
選取沒有直接 SVG 子系的 anchor
css
a:not(:has(> svg)) { ... }
選取直接同層級 input
的 label
。方向不對!
css
label:has(+ input) { … }
選取子元素「img
」不含 alt
文字的「article
」
css
article:has(img:not([alt])) { … }
選取在 DOM 中顯示某些狀態的 documentElement
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
選取格線中未懸停的所有項目
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
選取所有符合自訂元素之容器的容器<todo-list>
css
main:has(todo-list) { ... }
選取同時符合自訂元素之位置的容器<todo-list>
css
main:has(todo-list) { ... }
選取 3 個直接元素符合的 {11/1}
article
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
a
hr
css
p:has(+ hr) a:only-child { … }
css
article:has(>h1):has(>h2) { … }
選取標題後面加上標題的 article
css
article:has(> h1 + h2) { … }
在觸發互動狀態時選取「:root
」
css
:root:has(a:hover) { … }
選取 figure
後方不含 figcaption
的段落
css
figure:not(:has(figcaption)) + p { … }
你對 :has()
有什麼有趣的用途?這裡有意思的一點是,可以鼓勵你打破精神模式。您會覺得:「我可以改變這些風格嗎?」。
示例
以下將舉例說明運用方式。
資訊卡
觀看經典的卡片示範。可在資訊卡中顯示任何資訊,例如標題、副標題或某些媒體。這是基本資訊卡
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
如要引進一些媒體內容,該怎麼做?在這種設計中,資訊卡可以分割成兩個資料欄。在此之前,您可以建立新類別來代表這項行為,例如 card--with-media
或 card--two-columns
。這些類別名稱不僅容易產生混淆,也變得難以維護和記住。
透過 :has()
,您可以偵測資訊卡是否有媒體,並且執行適當操作。不需要修飾符類別名稱,
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
不必留在那裡。你可以盡情發揮創意。顯示「精選」內容的資訊卡如何配合版面配置調整?這個 CSS 會將精選資訊卡設為版面配置的完整寬度,並將其置於格狀起點。
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
如果精選資訊卡中設有橫幅,該如何吸引目光?
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
無限想像可能
表單
表單有何功用?以不容易設計樣式而聞名。例如樣式輸入內容及其標籤就屬於這類情形。舉例來說,Google 如何表明欄位有效?有了 :has()
,這就能事半功倍。我們可以掛接到相關虛擬類別,例如 :valid
和 :invalid
。
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
請試試看這個例子:試著輸入有效值和無效值,然後著重說明和停用焦點。
您也可以使用 :has()
顯示及隱藏欄位的錯誤訊息。輸入「電子郵件」欄位群組並新增錯誤訊息。
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
根據預設,系統會隱藏錯誤訊息。
.form-group__error {
display: none;
}
但當該欄位變成 :invalid
且未聚焦時,便可顯示訊息,而不需要額外的類別名稱。
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
使用者與您的表單互動時,都無法體驗其中美感。請參考這個範例。為微互動輸入有效值時,別忘了觀察這些指標。:invalid
值會導致表單群組搖動。但前提是使用者沒有動作偏好設定。
內容
請參考這個程式碼範例。不過,該如何在文件流程中使用 :has()
?這項工具彙整出一些想法,可以據此設定媒體的字體排版樣式。
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
此範例包含圖形。當沒有 figcaption
時,這些元素會懸浮在內容中。如果有 figcaption
,就會佔滿完整寬度並獲得額外的邊界。
回應狀態
如何在標記中將您的樣式回應某個狀態。以「傳統」滑動導覽列為例,如果有一個用來開啟導覽的切換按鈕,則可能會使用 aria-expanded
屬性。JavaScript 可以用來更新合適的屬性。當 aria-expanded
為 true
時,請使用 :has()
偵測這個項目,並更新滑動導覽的樣式。JavaScript 會執行本身的部分,CSS 可以運用這些資訊來完成所需作業。無須變更標記順序或額外加入類別名稱等 (注意:這不是可用於實際工作環境的範例)。
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
: 是否有助於避免使用者錯誤?
這些例子有什麼共通點?除了展示如何使用 :has()
的方法,他們也完全不需要修改類別名稱。每個插入了新內容並更新屬性。這是 :has()
的一大優點,因為它有助於減少使用者錯誤。使用 :has()
CSS 時,您可以負責在 DOM 中修改修改內容。您不需要在 JavaScript 中雜亂類別名稱,因此不太可能出現開發人員錯誤。我們幾乎都可以在輸入類別名稱時,因為需要在 Object
查詢中保留這些名稱。
這個想法很有趣,能引導我們採用更簡潔的標記和更少程式碼嗎?減少 JavaScript,因為我們進行的 JavaScript 調整不夠頻繁。較少 HTML,因為不再需要 card card--has-media
等類別。
跳脫思維框架
如上所述,:has()
鼓勵你打破心理模型。讓你有機會嘗試不同的事物其中一種嘗試突破極限的方法是只使用 CSS 設計遊戲機制。例如,您可以使用表單和 CSS 來建立步驟式機制。
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
還可發掘更多有趣的應用方式。您可以用它來掃遍具有變形的表單。請注意,建議在另一個瀏覽器分頁中查看此示範。
那經典的有線電線遊戲還好玩嗎?使用 :has()
可更輕鬆地建立這項機制。如果將遊標懸停在電線上方,遊戲就結束了。可以,我們可以使用同層合併器 (+
和 ~
) 建立部分遊戲機制。不過,使用 :has()
可以達到相同的結果,無需使用有趣的標記「手法」。請注意,建議在另一個瀏覽器分頁中查看此示範。
雖然您不會馬上將這些內容導入實際工作環境,但這些重點會特別註明使用基本功能的方式。例如能夠鏈結 :has()
。
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
效能和限制
在結束之前,你無法使用 :has()
做什麼?:has()
有一些限制。主要因為受到成效影響而出現。
- 您無法
:has()
:has()
。但您可以鏈結:has()
。css :has(.a:has(.b)) { … }
:has()
css :has(::after) { … } :has(::first-letter) { … }
內不得使用虛擬元素- 限制在只接受複合選取器
css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
的虛擬環境中使用:has()
- 限制在
css ::part(foo):has(:focus) { … }
虛擬元素之後使用:has()
- 使用
:visited
一律為 falsecss :has(:visited) { … }
如要瞭解與 :has()
相關的實際成效指標,請參閱這個 Glitch。感謝 Byungwoo 分享這些深入分析資訊和實作相關詳細資訊。
就是這麼簡單!
準備好迎接 :has()
。向朋友介紹並分享這篇文章,你的 CSS 發展策略將大有斬獲。
所有示範內容可在這個 CodePen 衝突中取得。