淺談邏輯選擇器 -- 父選擇器它來了!

来源:https://www.cnblogs.com/coco1s/archive/2022/05/18/16283836.html
-Advertisement-
Play Games

在 CSS 選擇器家族中,新增這樣一類比較新的選擇器 -- 邏輯選擇器,目前共有 4 名成員: :is :where :not :has 本文將帶領大家瞭解、深入它們。做到學以致用,寫出更現代化的選擇器。 :is 偽類選擇器 :is() CSS偽類函數將選擇器列表作為參數,並選擇該列表中任意一個選擇 ...


在 CSS 選擇器家族中,新增這樣一類比較新的選擇器 -- 邏輯選擇器,目前共有 4 名成員:

  • :is
  • :where
  • :not
  • :has

本文將帶領大家瞭解、深入它們。做到學以致用,寫出更現代化的選擇器。


:is 偽類選擇器

:is() CSS偽類函數將選擇器列表作為參數,並選擇該列表中任意一個選擇器可以選擇的元素。

在之前,對於多個不同父容器的同個子元素的一些共性樣式設置,可能會出現如下 CSS 代碼:

header p:hover,
main p:hover,
footer p:hover {
color: red;
cursor: pointer;
}

而如今有了 :is() 偽類,上述代碼可以改寫成:

:is(header, main, footer) p:hover {
color: red;
cursor: pointer;
}

它並沒有實現某種選擇器的新功能,更像是一種語法糖,類似於 JavaScript ES6 中的 Class() 語法,只是對原有功能的重新封裝設計,實現了更容易的表達一個操作的語法,簡化了某些複雜代碼的寫法。

語法糖(syntactic sugar)是指編程語言中可以更容易的表達一個操作的語法,它可以使程式員更加容易去使用這門語言,操作可以變得更加清晰、方便,或者更加符合程式員的編程習慣。用比較通俗易懂的方式去理解就是,在之前的某個語法的基礎上改變了一種寫法,實現的功能相同,但是寫法不同了,主要是為了讓開發人員在使用過程中更方便易懂。

一圖勝前言(引用至 New CSS functional pseudo-class selectors :is() and :where()):

支持多層層疊連用

再來看看這種情況,原本的 CSS 代碼如下:

<div><i>div i</i></div>
<p><i>p i</i></p>
<div><span>div span</span></div>
<p><span>p span</span></p>
<h1><span>h1 span</span></h1>
<h1><i>h1 i</i></h1>

如果要將上述 HTML 中,<div><p> 下的 <span><i> 的 color 設置為 red,正常的 CSS 可能是這樣:

div span,
div i,
p span,
p i {
color: red;
}

有了 :is() 後,代碼可以簡化為:

:is(div, p) :is(span, i) {
color: red;
}

結果如下:

這裡,也支持 :is() 的層疊連用。通過 :is(div, p) :is(span, i) 的排列組合,可以組合出上述 4 行的選擇器,達到同樣的效果。

當然,這個例子比較簡單,看不出 :is() 的威力。下麵這個例子就比較明顯,這麼一大段 CSS 選擇器代碼:

ol ol ul, ol ul ul, ol menu ul, ol dir ul,
ol ol menu, ol ul menu, ol menu menu, ol dir menu,
ol ol dir, ol ul dir, ol menu dir, ol dir dir,
ul ol ul, ul ul ul, ul menu ul, ul dir ul,
ul ol menu, ul ul menu, ul menu menu, ul dir menu,
ul ol dir, ul ul dir, ul menu dir, ul dir dir,
menu ol ul, menu ul ul, menu menu ul, menu dir ul,
menu ol menu, menu ul menu, menu menu menu, menu dir menu,
menu ol dir, menu ul dir, menu menu dir, menu dir dir,
dir ol ul, dir ul ul, dir menu ul, dir dir ul,
dir ol menu, dir ul menu, dir menu menu, dir dir menu,
dir ol dir, dir ul dir, dir menu dir, dir dir dir {
list-style-type: square;
}

可以利用 :is() 優化為:

:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) {
list-style-type: square;
}

不支持偽元素

有個特例,不能用 :is() 來選取 ::before::after 兩個偽元素。譬如:

註意,僅僅是不支持偽元素,偽類,譬如 :focus:hover 是支持的。

div p::before,
div p::after {
content: "";
//...
}

不能寫成:

div p:is(::before, ::after) {
content: "";
//...
}

:is 選擇器的優先順序

看這樣一種有意思的情況:

<div>
<p class="test-class" id="test-id">where & is test</p>
</div>
<div>
<p class="test-class">where & is test</p>
</div>

我們給帶有 .test-class 的元素,設置一個預設的顏色:

div .test-class {
color: red;
}

如果,這個時候,我們引入 :is() 進行匹配:

div :is(p) {
color: blue;
}

此時,由於 div :is(p) 可以看成 div p,優先順序是沒有 div .test-class 高的,因此,被選中的文本的顏色是不會發生變化的。

但是,如果,我們在 :is() 選擇器中,加上一個 #test-id,情況就不一樣了。

div :is(p, #text-id) {
color: blue;
}

按照理解,如果把上述選擇器拆分,上述代碼可以拆分成:

div p {
color: blue;
}
div #text-id {
color: blue;
}

那麼,我們有理由猜想,帶有 #text-id<p> 元素由於有了更高優先順序的選擇器,顏色將會變成 blue,而另外一個 div p 由於優先順序不夠高的問題,導致第一段文本依舊是 green

但是,這裡,神奇的是,兩段文本都變成了 blue

CodePen Demo -- the specificity of CSS :is selector

這是由於,:is() 的優先順序是由它的選擇器列表中優先順序最高的選擇器決定的。我們不能把它們割裂開來看。

對於 div :is(p, #text-id)is:() 內部有一個 id 選擇器,因此,被該條規則匹配中的元素,全部都會應用 div #id 這一級別的選擇器優先順序。這裡非常重要,再強調一下,對於 :is() 選擇器的優先順序,我們不能把它們割裂開來看,它們是一個整體,優先順序取決於選擇器列表中優先順序最高的選擇器

:is 的別名 :matches() 與 :any()

:is() 是最新的規範命名,在之前,有過有同樣功能的選擇,分別是:

:is(div, p) span {}
// 等同於
:-webkit-any(div, p) span {}
:-moz-any(div, p) span {}
:matches(div, p) span {}

當然,下麵 3 個都已經廢棄,不建議再繼續使用。而到今天(2022-04-27):is() 的相容性已經非常不錯了,不需要相容 IE 系列的話可以考慮開始用起來(配合 autoprefixer),看看 CanIUse

:where 偽類選擇器

瞭解了 :is 後,我們可以再來看看 :where,它們兩個有著非常強的關聯性。:where 同樣是將選擇器列表作為其參數,並選擇可以由該列表中的選擇器之一選擇的任何元素。

還是這個例子:

:where(header, main, footer) p:hover {
color: red;
cursor: pointer;
}

上述的代碼使用了 :where,可以近似的看為:

header p:hover,
main p:hover,
footer p:hover {
color: red;
cursor: pointer;
}

這就有意思了,這不是和上面說的 :is 一樣了麽?

那麼它們的區別在什麼地方呢?

:is:where 的區別

首先,從語法上,:is:where 是一模一樣的。它們的核心區別點在於 優先順序

來看這樣一個例子:

<div>
<p>where & is test</p>
</div>

CSS 代碼如下:

:is(div) p {
color: red;
}
:where(div) p {
color: green;
}

正常按我們的理解而言,:is(div) p:where(div) p 都可以轉化為 div p,由於 :where(div) p 後定義,所以文字的顏色,應該是 green 綠色,但是,實際的顏色表現為 color: red 紅色:

這是因為,:where():is() 的不同之處在於,:where() 的優先順序總是為 0 ,但是 :is() 的優先順序是由它的選擇器列表中優先順序最高的選擇器決定的。

上述的例子還不是特別明顯,我們再稍微改造下:

<div id="container">
<p>where & is test</p>
</div>

我們給 div 添加上一個 id 屬性,改造上述 CSS 代碼:

:is(div) p {
color: red;
}
:where(#container) p {
color: green;
}

即便如此,由於 :where(#container) 的優先順序為 0,因此文字的顏色,依舊為紅色 red。:where() 的優先順序總是為 0 這一點在使用的過程中需要牢記。

組合、嵌套

CSS 選擇器的一個非常大的特點就在於組合嵌套。:is:where 也不例外,因此,它們也可以互相組合嵌套使用,下述的 CSS 選擇器都是合理的:

/* 組合*/
:is(h1,h2) :where(.test-a, .test-b) {
text-transform: uppercase;
}
/* 嵌套*/
.title:where(h1, h2, :is(.header, .footer)) {
font-weight: bold;
}

這裡簡單總結下,:is:where 都是非常好的分組邏輯選擇器,唯一的區別在於:where() 的優先順序總是為 0,而:is() 的優先順序是由它的選擇器列表中優先順序最高的選擇器決定的。

:not 偽類選擇器

下麵我們介紹一下非常有用的 :not 偽類選擇器。

:not 偽類選擇器用來匹配不符合一組選擇器的元素。由於它的作用是防止特定的元素被選中,它也被稱為反選偽類(negation pseudo-class)。

舉個例子,HTML 結構如下:

<div class="a">div.a</div>
<div class="b">div.b</div>
<div class="c">div.c</div>
<div class="d">div.d</div>
div:not(.b) {
color: red;
}

div:not(.b) 它可以選擇除了 class 為 .b 元素之外的所有 div 元素:

MDN 的錯誤例子?一個有意思的現象

有趣的是,在 MDN 介紹 :not 的頁面,有這樣一個例子:

/* Selects any element that is NOT a paragraph */
:not(p) {
color: blue;
}

意思是,:not(p) 可以選擇任何不是 <p> 標簽的元素。然而,上面的 CSS 選擇器,在如下的 HTML 結構,實測的結果不太對勁。

<p>p</p>
<div>div</div>
<span>span</span>
<h1>h1</h1>

結果如下:

意思是,:not(p) 仍然可以選中 <p> 元素。我嘗試了多個瀏覽器,得到的效果都是一致的。

CodePen Demo -- :not pesudo demo

這是為什麼呢?這是由於 :not(p) 同樣能夠選中 <body>,那麼 <body> 的 color 即變成了 blue,由於 color 是一個可繼承屬性,<p> 標簽繼承了 <body> 的 color 屬性,導致看到的 <p> 也是藍色。

我們把它改成一個不可繼承的屬性,試試看:

/* Selects any element that is NOT a paragraph */
:not(p) {
border: 1px solid;
}

OK,這次 <p> 沒有邊框體現,沒有問題!實際使用的時候,需要註意這一層繼承的問題!

:not 的優先順序問題

下麵是一些使用 :not 需要註意的問題。

:not:is:where 這幾個偽類不像其它偽類,它不會增加選擇器的優先順序。它的優先順序即為它參數選擇器的優先順序。

並且,在 CSS Selectors Level 3:not() 內只支持單個選擇器,而從 CSS Selectors Level 4 開始,:not() 內部支持多個選擇器,像是這樣:

/* CSS Selectors Level 3,:not 內部如果有多個值需要分開 */
p:not(:first-of-type):not(.special) {
}
/* CSS Selectors Level 4 支持使用逗號分隔*/
p:not(:first-of-type, .special) {
}

:is() 類似,:not() 選擇器本身不會影響選擇器的優先順序,它的優先順序是由它的選擇器列表中優先順序最高的選擇器決定的。

:not(*) 問題

使用 :not(*) 將匹配任何非元素的元素,因此這個規則將永遠不會被應用。

相當於一段沒有任何意義的代碼。

:not() 不能嵌套 :not()

禁止套娃。:not 偽類不允許嵌套,這意味著 :not(:not(...)) 是無效的。

:not() 實戰解析

那麼,:not() 有什麼特別有意思的應用場景呢?我這裡列舉一個。

W3 CSS selectors-4 規範 中,新增了一個非常有意思的 :focus-visible 偽類。

:focus-visible 這個選擇器可以有效地根據用戶的輸入方式(滑鼠 vs 鍵盤)展示不同形式的焦點。

有了這個偽類,就可以做到,當用戶使用滑鼠操作可聚焦元素時,不展示 :focus 樣式或者讓其表現較弱,而當用戶使用鍵盤操作焦點時,利用 :focus-visible,讓可獲焦元素獲得一個較強的表現樣式。

看個簡單的 Demo:

<button>Test 1</button>
button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}

使用滑鼠點擊:

可以看到,使用滑鼠點擊的時候,觸發了元素的 :active 偽類,也觸發了 :focus偽類,不太美觀。但是如果設置了 outline: none 又會使鍵盤用戶的體驗非常糟糕。因為當鍵盤用戶使用 Tab 嘗試切換焦點的時候,會因為 outline: none 而無所適從。

因此,可以使用 :focus-visible 偽類改造一下:

button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}
button:focus:not(:focus-visible) {
outline: none;
}

看看效果,分別是在滑鼠點擊 Button 和使用鍵盤控制焦點點擊 Button:

CodePen Demo -- :focus-visible example

可以看到,使用滑鼠點擊,不會觸發 :foucs,只有當鍵盤操作聚焦元素,使用 Tab 切換焦點時,outline: 2px solid red 這段代碼才會生效。

這樣,我們就既保證了正常用戶的點擊體驗,也保證了無法使用滑鼠的用戶的焦點管理體驗,在可訪問性方面下了功夫。

值得註意的是,這裡為什麼使用了 button:focus:not(:focus-visible) 這麼繞的寫法而不是直接這樣寫呢:

button:focus {
outline: unset;
}
button:focus-visible {
outline: 2px solid red;
}

解釋一下,button:focus:not(:focus-visible) 的意思是,button 元素觸發 focus 狀態,並且不是通過 focus-visible 觸發,理解過來就是在支持 :focus-visible 的瀏覽器,通過滑鼠激活 :focus 的 button 元素,這種情況下,不需要設置 outline

為的是相容不支持 :focus-visible 的瀏覽器,當 :focus-visible 不相容時,還是需要有 :focus 偽類的存在。

因此,這裡藉助 :not() 偽類,巧妙的實現了一個實用效果的方案降級。

這裡有點繞,需要好好理解理解。

:not 相容性

經歷了 CSS Selectors Level 3 & CSS Selectors Level 4 兩個版本,到今天(2020-05-04),除去 IE 系列,:not 的相容性已經非常之好了:

:has 偽類選擇器

OK。最後到所有邏輯選擇器裡面最重磅的 :has 出場了。它之所以重要是因為它的誕生,填補了在之前 CSS 選擇器中,沒有核心意義上真正的父選擇器的空缺。

:has 偽類接受一個選擇器組作為參數,該參數相對於該元素的 :scope 至少匹配一個元素。

實際看個例子:

<div>
<p>div -- p</p>
</div>
<div>
<p class="g-test-has">div -- p.has</p>
</div>
<div>
<p>div -- p</p>
</div>
div:has(.g-test-has) {
border: 1px solid #000;
}

我們通過 div:has(.g-test-has) 選擇器,意思是,選擇 div 下存在 class 為 .g-test-has 的 div 元素。

註意,這裡選擇的不是 :has() 內包裹的選擇器選中的元素,而是使用 :has() 偽類的宿主元素。

效果如下:

可以看到,由於第二個 div 下存在 class 為 .g-test-has 的元素,因此第二個 div 被加上了 border。

:has() 父選擇器 -- 嵌套結構的父元素選擇

我們再通過幾個 DEMO 加深下印象。:has() 內還可以寫的更為複雜一點。

<div>
<span>div span</span>
</div>

<div>
<ul>
<li>
<h2><span>div ul li h2 span</span></h2>
</li>
</ul>
</div>

<div>
<h2><span>div h2 span</span></h2>
</div>
div:has(>h2>span) {
margin-left: 24px;
border: 1px solid #000;
}

這裡,要求準確選擇 div 下直接子元素是 h2,且 h2 下直接子元素有 span 的 div 元素。註意,選擇的最上層使用 :has() 的父元素 div。結果如下:

這裡體現的是嵌套結構,精確尋找對應的父元素

:has() 父選擇器 -- 同級結構的兄元素選擇

還有一種情況,在之前也比較難處理,同級結構的兄元素選擇。

看這個 DEMO:

<div class="has-test">div + p</div>
<p>p</p>

<div class="has-test">div + h1</div>
<h1>h1</h1>

<div class="has-test">div + h2</div>
<h2>h2</h2>

<div class="has-test">div + ul</div>
<ul>ul</ul>

我們想找到兄弟層級關係中,後面接了 <h2> 元素的 .has-test 元素,可以這樣寫:

.has-test:has(+ h2) {
margin-left: 24px;
border: 1px solid #000;
}

效果如下:

這裡體現的是兄弟結構,精確尋找對應的前置兄元素

這樣,一直以來,CSS 沒有實現的父選擇器,藉由 :has() 開始,也能夠做到了。這個選擇器,能夠極大程度的提升開發體驗,解決之前需要比較多 JavaScript 代碼才能夠完成的事。

上述 DEMO 彙總,你可以戳這裡 CodePen Demo -- :has Demo

:has() 相容性,給時間一點時間

比較可惜的是,:has() 在最近的 Selectors Level 4 規範中被確定,目前的相容性還比較慘淡,截止至 2022-05-04,Safari 和 最新版的 Chrome(V101,可通過開啟 Experimental Web Platform features 體驗)

Chrome 下開啟該特性需要,1. 瀏覽器 URL 框輸入 chrome://flags,2. 開啟 #enable-experimental-web-platform-features

耐心等待,給給時間一點時間,這麼好的選擇器馬上就能大規模應用了。

最後

本文到此結束,希望對你有幫助

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文介紹如何利用 SQL UNION 操作符將多條 SELECT 語句組合成一個結果集。使用 UNION 可極大地簡化複雜的 WHERE 子句,簡化從多個表中檢索數據的工作。 一、組合查詢 多數 SQL 查詢只包含從一個或多個表中返回數據的單條 SELECT 語句。但是,SQL 也允許執行多個查詢( ...
  • 本文介紹自聯結(self-join)、自然聯結(natural join)和外聯結 (outer join),包括它們的含義和使用方法。介紹如何使用表別名,如何對被聯結的表使用聚集函數。 一、使用表別名 SQL 如何創建計算欄位 介紹瞭如何使用別名引用被檢索的表列。給列起別名的語法如下: SELEC ...
  • JSONOBJECT解析數據 若JSON格式數據如下所示: [{"id":"5","version":"5.5","name":"Clash of Clans"}, {"id":"6","version":"6.0","name":"Boom Beachx"}] 可以看到要解析的數據是一個JSON數 ...
  • 我們在直播中,為了增強真實感,烘托場景氛圍需要播放的簡短效果音。例如:掌聲、笑聲、禮物音效、提示音等。在游戲中,有時也需要播放子彈聲、碰撞打擊聲等。 ZegoExpress SDK 提供音效文件播放器,通過 ZegoAudioEffectPlayer 統一管理音效,支持音效播放(可以多音效重疊播放... ...
  • 在產品運營的工作過程中,需要每日關註產品的核心指標變化情況,監控其整體運營狀況。華為分析服務提供查看吸引新用戶卡片,該卡片展示了新增用戶數、人均會話次數、人均訪問時長、人均頁面訪問數。藉助該頁面運營可觀察拉新效果,判斷產品對新用戶的吸引力。 問題描述 某開發者在集成華為分析服務後,發現AGC概覽頁面 ...
  • 現在正式回歸,開始好好做項目了,正好這一個項目也開始慢慢的開始起色了,前面的準備工作都做的差不多了。 而且我現在也開始慢慢瞭解到了一些項目才開始需要的一些什麼東西了,vuex、router這些都是必備的,後期一定要練得非常熟練才行。 一.重寫push/replace方法 有一個編程式導航的bug,當 ...
  • 常用事件 onload <script> window.onload = function () { ele = document.getElementById("i") console.log(ele.innerHTML); } </script> </head> <body> <div clas ...
  • DOM DOM document Object Model 文檔對象模型 // 整個html文檔,會保存一個文檔對象document // console.log( document ); // 獲取當前文檔的對象 查找標簽 直接查找 document.getElementsByTagName("標 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...