CSS 的 :has 選取器介紹
今日為大家介紹 CSS 的一個新成員— :has 選取器。
:has 選取器自 2015 年起已加入至新 CSS 標準的草稿,並經過多年討論,這個選取器剛剛在 Safari 的技術預覽(Technical Preview)版本 137 實現了。 而其他瀏覽器則仍未推出。
過去三十年,CSS 選取器一直都是向後或向下尋找的,即所有選取器條件,都是應用到條件最後的元素上。這是配合瀏覽器邊下載邊渲染元素的做法,瀏覽器可以在未載入之後的元素時,便已可以應用 CSS 樣式。而 :has 選取器則是應用到選取條件中中間的元素,因而 :has 選取器又被稱為父類選取器。
可以想像,這個選取器和之前一直使用的選取器邏輯算法都不同,這也是為甚麼這麼多年討論,還未大規模落實的原因之一。但通過是次技術預覽版本實現,我們可以先了解這個選取器的應用,實驗一下可以引申出哪些新 CSS 用法,及我如何在 Slides.com 的簡報中使用這個 :has 選取器。
👨🏻🏫 :has 選取器介紹
那 :has 選取器是長甚麼樣子的?類似這樣:
有直接 img 元素的 a 元素
a:has(>img)
或是只有一個 img 元素的 a 元素
a:has(img:only-child)
或是配合 :checked, :invalid 等狀態選取器,對包著 input 輸入框的元素進行樣式設定。
label:has(:invalid)
🔬 :has 選取器實驗
我做了幾個實驗例子,實驗各種 :has 選取器的穩定性。
1️⃣ 實驗一:只選擇有 figcatpion 的 figure:figure:has(figcaption)
HTML
<section id="demo-1">
<figure>
<img src="https://placekitten.com/300/300" alt="Placeholder">
<figcaption>Figcaption</figcaption>
</figure>
<figure>
<img src="https://placekitten.com/300/300" alt="Placeholder">
</figure>
</section>
CSS
figure {
border: 3px solid #fcfcfc;
}
figure:has(figcaption) {
border: 3px solid lightgrey;
background: lightgrey;
display: inline-block;
text-align: center;
padding: .5em;
border-radius: 5px;
}
❌ 測試失敗,連沒有 figcaption 的 figure 元素,也會間歇性生效。即選取結果未穩定。
2️⃣ 實驗二:數量選取器: ul:has(li:nth-child(4))
這個選取器如成功將會很實用。可以用在導航列、新聞列表等容器上,並按其中有多少子元素而決定不同的排版。
HTML
<section id="demo-5">
<p>When there are 3 or less children:</p>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<p>When there are more than 3 children, layout changes to 50%, 50% split:</p>
<p>✅ Working as expected.</p>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>
</section>
CSS
#demo-5 ul {
list-style: none;
margin: 1em 0;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
#demo-5 li {
background: lightgrey;
padding: .5em;
border-bottom: 2px solid darkgrey;
}
#demo-5 ul:has(li:nth-child(4)) {
grid-template-columns: 1fr 1fr;
}
✅ 成功
3️⃣ 實驗三::has(:invalid) 及 :has(:checked)
這個實驗測試是否可以按 :invalid, :checked 等輸入框狀態,然後對包著這輸入框的 label 元素設定樣式。可惜,實驗結果未能成功,當輸入框的狀態改變時,這個選取器未有更新,但在測試中有曾經成功選擇過,估計是負責更新的算法尚待改善。
將來此用法若成熟,可以引申出不同的用法,例如只有最少選取任何勾選框,提交按鈕才顯示,或有不同的狀態時有更豐富的顯示效果,而不是只局限於輸入框或輸入框後的元素(+寫法)等。
HTML
<section id="demo-3">
<h2>:has(:invalid)</h2>
<p>
<label>
Any numbers here:
<input type="text" pattern="\d*">
</label>
</p>
<p>
<label>
YYYY here:
<input type="text" pattern="\d{4}">
</label>
</p>
</section>
<section id="demo-4">
<h2>:has(:checked)</h2>
<p>
<label>
<input type="radio" name="gender">
Male
</label>
</p>
<p>
<label>
<input type="radio" name="gender">
Female
</label>
</p>
</section>
CSS
#demo-3 label:has(:invalid) {
border-left-color: red;
}
#demo-4 label:has(:checked) {
border-left-color: green;
}
❌ 測試失敗
4️⃣ 實驗四:有圖片的超連結:a:has(img:only-child)
圖片元素因為是替代型元素,所以沒有 :after 及 :before 的偽元素可使用。所以一般為圖片做裝飾的做法是加一個元素包著 img,再在這個父元素設定樣式。有了 :has 選取器,就可以直接選最這些只包著 img 元素的父元素,例如 a:has(img:only-child)。
例如以下 CSS 樣式設定一個背景紋理,並在滑鼠移入時作背景移動。
HTML:
<a href="#">
<img src="https://placekitten.com/300/300" alt="Placeholder">
</a>
CSS:
/* 選取只有一個 img 元素的 a 元素 */
a:has(img:only-child) {
display: inline-block;
position: relative;
}
/* 移除 inline 圖片下方會有些少空白的問題 */
a:has(img:only-child) img {
display: block;
}
/* 設定 :before, :after 偽元素基本樣式 */
a:has(img:only-child):before,
a:has(img:only-child):after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-size: 10px 10px;
z-index: -1;
}
/* 設定 :before 偽元素背景紋理,但位置為 0,0 故未可見。 */
a:has(img:only-child):before {
top: 0;
left: 0;
background-image: radial-gradient(lightblue 60%, transparent 60%);
}
/* 設定 :after 偽元素背景紋理 */
a:has(img:only-child):after {
bottom: -10px;
right: -10px;
background-image: radial-gradient(steelblue 60%, transparent 60%);
z-index: -1;
}
/* 設定滑鼠移入時的 :before, :after 偽元素的新位置 */
a:has(img:only-child):hover:before {
top: -10px;
left: -10px;
}
a:has(img:only-child):hover:after {
bottom: -20px;
right: -20px;
}
✅ a:has(img:only-child) 成功
🎞 Slides.com 自定義 CSS 樣式中使用 :has 選取器
我平常製作簡報,會使用 Slides.com,當中可以允許我自定義客製 CSS 樣式。也可以讓我為簡報上的物作元素加入 class 類別名稱。但由於其背後的 reveal.js 結構,使我若想使用 CSS 樣式統一不同類別的物件,就必須為每一個物件加入類別名稱,對大型簡報製作尤顯費時。
以下為 reveal.js 的簡報元素結構範例。
<section class="present" style="display: block;">
<div
class="sl-block"
data-block-type="text"
style="width: 960px; left: 0px; top: 0px; height: auto;"
>
<div
class="sl-block-content"
data-placeholder-tag="h1"
data-placeholder-text="Title Text"
style="z-index: 11;"
>
<h1>And make ".bg" block full width (as a bg)</h1>
</div>
</div>
<div
class="sl-block"
data-block-type="shape"
style="width: 300px; height: 300px; left: 330px; top: 200px;"
>
<div
class="sl-block-content bg"
data-shape-type="symbol-smiley"
data-shape-fill-color="rgb(217, 234, 211)"
data-shape-stretch="false"
style="z-index: 10;"
>
<svg>...</svg>
</div>
</div>
</section>
從上述的結構中可以看出,簡報中的每個物件元素,共有三層 DIV,最出面的 .sl-block 決定在簡報中的位置,中間的 .sl-block-content 是自定義 class 類別名稱的設定位置,上方的 "bg" 是自定義的。而第三層則是內容本身。
我一直希望可以統一限制所有相同類型元素的位置,就像套用範本般,例如我希望 h1 都是垂直水平置中,所有的 bg 類別佔據全畫面及 -1 zindex 等。但由於決定位置及尺寸的是最上層的 div,所以唯有使用 :has 選取器來解決。
所以,當我加入以下 CSS 後,就可以設定 h1 及有 .bg 類別的元素位置了。
.sl-block:has(.bg) {
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}
.sl-block:has(.center),
.sl-block:has(h1) {
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
.sl-block-content {
display: grid;
place-items: center;
}
}
然而,現在上述 CSS 樣式只在 Safari 技術預覽版本中能運作,真的有用嗎?有。我拍攝一些教學片時,就會使用 slides.com 預先製作好簡報,再投影錄製成教學片,所以這些簡報的作用是拍攝道具之一,而不是用作分享的,故我自己使用的瀏覽器能支援便足夠,而且拍攝的簡報通常會快速製作,這更突顯套用 CSS 樣式直接全域設定不同類別元素位置的作用。
總結,:has 選取器剛剛在一個測試版瀏覽器上實作了,雖然距離全面使用估計還有段時間,起碼一年半載,但現時已經可以實驗性測試及配合 @supports 使用,兼且在特定場景,還真可以投入生產使用呢。
附上配合 @supports 使用的方法:
@supports selector(a:has(img)) {
.support{display: block;}
.not.support{display: none;}
}
上述實驗源代碼:
https://codepen.io/makzan/pen/GRMyzxQ
— 麥誠 Makzan,2021-12-29。
我是麥誠軒(Makzan),除了正職外,平常我要麼辦本地賽與辦世界賽,要麼任教編程與網站開發的在職培訓。現正轉型將面授培訓內容寫成電子書、網上教材等,至今撰寫了 7 本書, 2 個視頻教學課程。
如果我的文章有價值,請左下角 👍🏻按讚支持,或訂閱贊助我持續創作及分享。
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!


- 来自作者
- 相关推荐