:has():系列選取器

自從上次開始 (以 CSS 術語來說),我們嘗試了各種性質的串聯。我們做的樣式構成了「階層式樣式表」。我們的選取器也會以階層排列他們會傾斜。但在大多數情況下,用量會下降。但從來沒有向上傾聽。多年來,我們一直都喜歡「家長選取器」。現在終於來到這裡了!:has() 虛擬選取器的形狀。

如果任何以參數傳遞的選取器符合至少一個元素,則 :has() CSS 虛擬類別代表一個元素。

不僅僅是「家長」選取器程式碼這就是行銷的好方法也可能是「條件環境」選取器程式碼但那支的圓環不一樣。那「家人」呢選取工具?

瀏覽器支援

在我們進一步探討前,建議您先說明瀏覽器支援。還沒準備好。但它又離不開了。目前尚不支援 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()」能帶來許多商機。即使是可能還沒發現的也可能是。請參考以下建議。

選取含有直接 figcaptionfigure 元素。 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)) { ... } 選取格狀檢視畫面中所有未懸停的項目 css .grid:has(.grid__item:hover) .grid__item:not(:hover) { ... } 選取含有自訂元素的容器 <todo-list> css main:has(todo-list) { ... } 選取段落中含有直接同層級 hr 元素的每段單曲 acss p:has(+ hr) a:only-child { … }。 選取符合多項條件的article 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-mediacard--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;
}

無限可能

表單

表單怎麼辦?這些元素以設計不易的風格聞名。其中一個例子是樣式輸入項目及其標籤。舉例來說,要如何指出特定欄位是否有效?有了 :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-expandedtrue 時,使用 :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 調整。由於不再需要 card card--has-media 等類別,因此請減少 HTML

跳脫框架思考

如上所述,: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) { … }
  • 限制在虛擬物件中使用 :has(),只接受複合選取器 css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
  • 限制在虛擬元素後方使用 :has() css ::part(foo):has(:focus) { … }
  • 使用 :visited 一律為 false css :has(:visited) { … }

如需與「:has()」相關的實際成效指標,請查看此 Glitch。獎勵由 Byungwoo 提供,分享有關導入方式的洞察和詳細資訊。

就是這麼簡單!

:has()做好準備。請向好友介紹並分享這篇文章,說明我們如何運用 CSS 服務扭轉局勢。

如需所有示範內容,請參閱此 CodePen 衝突