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

来源: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
  • 前言 本文將以 C# 語言來實現一個簡單的布隆過濾器,為簡化說明,設計得很簡單,僅供學習使用。 感謝@時總百忙之中的指導。 布隆過濾器簡介 布隆過濾器(Bloom filter)是一種特殊的 Hash Table,能夠以較小的存儲空間較快地判斷出數據是否存在。常用於允許一定誤判率的數據過濾及防止緩存 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 「簡單有價值的事情長期堅持做」 這是成功最簡單,但也最難學的秘訣。不經過訓練,人很難意識到時間複利的威力。 仙劍奇俠傳的「十里坡劍神」和金庸群俠傳的「十級野球拳」,就是簡單的事情持之以恆反覆做,最後就有巨大的威力 唐家三少成為網文收入第一,最重要的一步是十四年從未斷日更 這樣的案例很多,一開始可能成 ...
  • 迎面走來了你的面試官,身穿格子衫,挺著啤酒肚,髮際線嚴重後移的中年男子。 手拿泡著枸杞的保溫杯,胳膊夾著MacBook,MacBook上還貼著公司標語:“我愛加班”。 面試開始,直入正題。 面試官: 看你簡歷上面寫著精通MySQL,我先問你事務的特性是什麼? 老生常談,這個還有誰不會背的嗎? 我: ...
  • 基礎知識 python是一門腳本語言,它是解釋執行的。 python使用縮進做為語法,而且python2環境下同一個py文件中不能同時存在tab和空格縮進,否則會出錯,建議在IDE中顯示縮進符。 python在聲明變數時不寫數據類型,可以type(xx)來獲取欄位的類型,然後可以int(),list ...
  • 為什麼要多線程下載 俗話說要以終為始,那麼我們首先要明確多線程下載的目標是什麼,不外乎是為了更快的下載文件。那麼問題來了,多線程下載文件相比於單線程是不是更快? 對於這個問題可以看下圖。 橫坐標是線程數,縱坐標是使用對應線程數下載對應文件時花費的時間,藍橙綠代表下載文件的大小,每個線程下載對應文件2 ...
  • 詳細講解python爬蟲代碼,爬微博搜索結果的博文數據。 爬取欄位: 頁碼、微博id、微博bid、微博作者、發佈時間、微博內容、轉發數、評論數、點贊數。 爬蟲技術: 1、requests 發送請求 2、datetime 時間格式轉換 3、jsonpath 快速解析json數據 4、re 正則表達式提... ...
  • 背景: 一般我們可以用HashMap做本地緩存,但是HashMap功能比較弱,不支持Key過期,不支持數據範圍查找等。故在此實現了一個簡易的本地緩存,取名叫fastmap。 功能: 1.支持數據過期 2.支持等值查找 3.支持範圍查找 4.支持key排序 實現思路: 1.等值查找採用HashMap2 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 本章是系列文章的第八章,用著色演算法進行寄存器的分配過程。 本文中的所有內容來自學習DCC888的學習筆記或者自己理解的整理,如需轉載請註明出處。周榮華@燧原科技 寄存器分配 寄存器分配是為程式處理的值找到存儲位置的問題 這些值可以存放到寄存器,也可以存放在記憶體中 寄存器更快,但數量有限 記憶體很多,但 ...