CSS的邏輯組合偽類

CSS 的邏輯組合偽類有 4 種,分別是::not()、:is()、:where()和:has()。

否定偽類:not()

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

也叫否定偽類放椰,是在元素與括號里面的參數(shù)不匹配的時候,就會對這個偽類進行匹配愉粤。比如:

:not(span):{color:red}

這就會匹配不是 span 元素的其他所有元素砾医,包括 html 和 body。

否定偽類:not()的幾個特點:

  1. :not()的優(yōu)先級是 0衣厘,因為它的優(yōu)先級是由括號里面的參數(shù)來定的如蚜;
  2. :not()偽類可以同時判斷多個選擇器,比如input:not(:disabled):not(:read-only)
    {}影暴,表示匹配不屬于禁用狀態(tài)同時也不處于只讀狀態(tài)的 input 元素错邦;
  3. not()支持多個表達式,比如:.cs-li:not(li, dd)
    {}型宙,還有另外一種寫法:.cs-li:not(li):not(dd) {}撬呢。但是這兩種寫法,要考慮兼容性問題早歇;
  4. :not()也支持選擇符倾芝,比如:input:not(.a > .b) { border: red solid; };

有趣的是,在 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>

結果如下:


v2-ccf368f6d606adf59177aae778441f32_720w.gif

結果:not(p)仍然可以選中 p 元素路翻。嘗試了多個瀏覽器,得到的效果都是一致的茄靠。

這是為什么呢茂契?這是由于 :not(p)同樣能夠選中 body,那么 body 的 color 即變成了 blue慨绳,由于 color 是一個可繼承屬性掉冶,p 標簽繼承了 body 的 color 屬性,導致看到的 p 也是藍色脐雪。

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

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

再看下效果:


QQ截圖20231205170453.png

OK,這次 p 標簽沒有邊框體現(xiàn)战秋,沒有問題璧亚!實際使用的時候,需要注意這一層繼承的問題脂信!

:not 的優(yōu)先級問題

下面是一些使用 :not 需要注意的問題癣蟋。

:not、:is狰闪、:where 這幾個偽類不像其它偽類梢薪,它不會增加選擇器的優(yōu)先級。它的優(yōu)先級即為它參數(shù)選擇器的優(yōu)先級尝哆。

并且秉撇,在 CSS Selectors Level 3,:not() 內(nèi)只支持單個選擇器秋泄,而從 CSS Selectors Level 4 開始琐馆,:not() 內(nèi)部支持多個選擇器,像是這樣:

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

與 :is() 類似瘦麸,:not() 選擇器本身不會影響選擇器的優(yōu)先級,它的優(yōu)先級是由它的選擇器列表中優(yōu)先級最高的選擇器決定的歧胁。

:not() 實戰(zhàn)解析

來一個特別的例子:

在 W3 CSS selectors-4 規(guī)范 中滋饲,新增了一個非常有意思的 :focus-visible 偽類厉碟。

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

有了這個偽類屠缭,就可以做到箍鼓,當用戶使用鼠標操作可聚焦元素時,不展示 :focus 樣式或者讓其表現(xiàn)較弱呵曹,而當用戶使用鍵盤操作焦點時款咖,利用 :focus-visible,讓可獲焦元素獲得一個較強的表現(xiàn)樣式奄喂。

看個簡單的 Demo:

<button>Test 1</button>
<button>Test 2</button>
<button>Test 3</button>

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

鼠標點擊效果:


awg8a-4hgtv.gif

可以看到铐殃,使用鼠標點擊的時候,觸發(fā)了元素的 :active 偽類跨新,也觸發(fā)了 :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:


anrg4-xa0zo.gif

可以看到,使用鼠標點擊许饿,不會觸發(fā) :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 元素觸發(fā) focus 狀態(tài)劲蜻,并且不是通過 focus-visible 觸發(fā)陆淀,理解過來就是在支持 :focus-visible 的瀏覽器,通過鼠標激活 :focus 的 button 元素先嬉,這種情況下轧苫,不需要設置 outline。

為的是兼容不支持 :focus-visible 的瀏覽器疫蔓,當 :focus-visible 不兼容時含懊,還是需要有 :focus 偽類的存在身冬。

因此,這里借助 :not() 偽類岔乔,巧妙的實現(xiàn)了一個實用效果的方案降級酥筝。

這里有點繞,需要好好理解理解重罪。

:not 兼容性

除去 IE 系列樱哼,:not 的兼容性已經(jīng)非常之好了


111116.jpg

任意匹配偽類:is()

:is()偽類哀九,是把括號里面的選擇都分配出去剿配。語法如下:

:is(article, section) p {}

:is(.article[class], section) p {}

.some-class:is(article:not([id]), section) p {}

is 這個偽類最大的作用,就是在簡化選擇器阅束。比如我們要設置多個 div 內(nèi)的圖片樣式呼胚,樣式代碼如下:

.div-a > img, .div-b > img, .div-c > img, .div-d > img {   
 display: block;    
 width: 100%; 
 height: 100%;  
 border-radius: 50%;
}

通過 is 偽類來簡化一下:

:is(.div-a,.div-b,.div-c,.div-d)>img{
   display: block; 
   width: 100%;
   height: 100%;  
   border-radius: 50%;
}

單個選擇器可以包含任意數(shù)量的:is()偽類。

例如息裸,下面的復雜選擇器將綠色文本顏色應用于所有h1蝇更、h2和p元素,這些元素是具有section或.primary類的.secondary的子元素呼盆,并且不是article的第一個子元素:

article section.primary:not(:first-child) h1,
article section.primary:not(:first-child) h2,
article section.primary:not(:first-child) p,
article section.secondary:not(:first-child) h1,
article section.secondary:not(:first-child) h2,
article section.secondary:not(:first-child) p {
  color: green;
}

用is可以寫為:

article section:not(:first-child):is(.primary, .secondary) :is(h1, h2, p) {
  color: green;
}
axn75-co7k1.gif
支持多層層疊連用

原本的 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() 的威力腿时。下面這個例子就比較明顯况脆,這么一大段 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() 優(yōu)化為:

:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) {
  list-style-type: square;
}
不支持偽元素

注意::is()不能匹配::before和::after偽元素

不能寫成:

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

:where()偽類

:where()偽類的功能和 is 是一樣的批糟,只是它的優(yōu)先級一直都是 0格了,會忽略括號內(nèi)參數(shù)的優(yōu)先級。比如:

:where(.article, section) p {

}

優(yōu)先級就是 p 標簽的優(yōu)先級徽鼎。

:where()的零特異性對于CSS重置可能是實用的盛末,當沒有特定的樣式可用時,CSS重置應用標準樣式的基線否淤。通常满败,重置會應用默認字體、顏色叹括、填充和邊距算墨。

例如:

/* reset */
:where(h2) {
  margin-block-start: 1em;
}

:where(article :first-child) {
  margin-block-start: 0;
}

你現(xiàn)在可以覆蓋任何CSS重置樣式,而不管它的特殊性汁雷,也不需要更多的選擇器或!important:

h2 {
  margin-block-start: 2em;
}

:is 和 :where 的區(qū)別

首先净嘀,從語法上报咳,:is 和 :where 是一模一樣的。它們的核心區(qū)別點在于 優(yōu)先級挖藏。

來看這樣一個例子:

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

css:

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

正常按我們的理解而言暑刃,:is(div) p 和 :where(div) p 都可以轉(zhuǎn)化為 div p,由于 :where(div) p 后定義膜眠,所以文字的顏色岩臣,應該是 green 綠色,但是宵膨,實際的顏色表現(xiàn)為 color: red 紅色:

這是因為架谎,:where() 和 :is() 的不同之處在于,:where() 的優(yōu)先級總是為 0 辟躏,但是 :is() 的優(yōu)先級是由它的選擇器列表中優(yōu)先級最高的選擇器決定的谷扣。

組合、嵌套

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 都是非常好的分組邏輯選擇器,唯一的區(qū)別在于:where() 的優(yōu)先級總是為 0籽御,而:is() 的優(yōu)先級是由它的選擇器列表中優(yōu)先級最高的選擇器決定的练慕。

關聯(lián)偽類:has()

:has() 偽類接受一個選擇器組作為參數(shù),該參數(shù)相對于該元素的 :scope 至少匹配一個元素篱蝇。就是匹配某個選擇器贺待。例如:

a:has(span) { 
  color:red 
}

a 元素內(nèi)的 span 標簽的字體顏色設置為 red。

例如:下面的CSS用于為任何包含一個或多個a或img標簽的section鏈接添加一個藍色的兩像素邊框:

a:has(img, section) {
  border: 2px solid blue;
}

例如:

<div>
    <p>div -- p</p>
</div>
<div>
    <p class="g-test-has">div -- p.has</p>
</div>
<div>
    <p>div -- p</p>
</div>

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

注意零截,這里選擇的不是 :has() 內(nèi)包裹的選擇器選中的元素麸塞,而是使用 :has() 偽類的宿主元素。

效果如下:


QQ截圖20231206110346.png

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

:has()的引入允許了過去沒有JavaScript就不可能實現(xiàn)的可能性弧哎。例如雁比,當任何必需的內(nèi)部字段無效時,您可以設置外部表單<fieldset>和以下提交按鈕的樣式:

/* 當任何所需的內(nèi)部字段無效時撤嫩,顯示紅色邊框 */
fieldset:has(:required:invalid) {
  border: 3px solid red;
}
/* 當無效時更改提交按鈕樣式 */
fieldset:has(:required:invalid) + button[type='submit'] {
  opacity: 0.2;
  cursor: not-allowed;
}

此示例添加包含子菜單項列表的導航鏈接子菜單指示符:

nav li:has(ol, ul) a::after {
  display: inlne-block;
  content: ">";
}

或者可以添加調(diào)試風格偎捎,比如突出顯示所有的<figure>元素,而不帶內(nèi)部的img:

figure:not(:has(img)) {
  border: 3px solid red;
}
:has() 父選擇器 -- 嵌套結構的父元素選擇

:has() 內(nèi)還可以寫的更為復雜一點。例如:

<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>

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

這里茴她,要求準確選擇 div 下直接子元素是 h2寻拂,且 h2 下直接子元素有 span 的 div 元素。注意丈牢,選擇的最上層使用 :has() 的父元素 div祭钉。結果如下:


QQ截圖20231206111026.png

這里體現(xiàn)的是嵌套結構,精確尋找對應的父元素己沛。

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

例如:

<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-test:has(+ h2) {
    margin-left: 24px;
    border: 1px solid #000;
}

效果如下:


QQ截圖20231206111456.png

這里體現(xiàn)的是兄弟結構晶姊,精確尋找對應的前置兄元素扒接。

這樣伪货,一直以來们衙,CSS 沒有實現(xiàn)的父選擇器,借由 :has() 開始碱呼,也能夠做到了蒙挑。這個選擇器,能夠極大程度的提升開發(fā)體驗愚臀,解決之前需要比較多 JavaScript 代碼才能夠完成的事忆蚀。

例如:在鼠標hover到一個項時,其前后兩項也會變化


as4o8-44yky.gif

關鍵代碼:

li:has(+li:hover), /* 選中被hover的li的前一個li姑裂,即當一個元素后面緊跟一個被hover的li時馋袜,選中它 */
li:hover + li {    /* 選中被hover的li的后一個li,這個是大家都知道的 */
  font-size: 1.5em;
  width: 40px;
  height: 40px;
}

還可以根據(jù)子元素的數(shù)量使用不同的布局


777_b.gif

關鍵代碼:

/* 意思是當ul中第二個子元素也是最后一個子元素時舶斧,選中ul里面的所有l(wèi)i欣鳖,讓它們的寬度為75,即半寬茴厉。
四個子元素時也做同樣處理
*/
ul:has(li:nth-child(2):last-child)   li,
ul:has(li:nth-child(4):last-child)   li {
  width: 75px;
  height: 75px;
}

ul:has(li:nth-child(8):last-child)   li {
  width: 37.5px;
  height: 37.5px;
}
/*
這個意思是當ul中正好有5個元素時泽台,選中其中的前面兩個,把寬度調(diào)為一半
*/
ul:has(li:nth-child(5):last-child)   li:nth-child(-n + 2) {
  width: 75px;
  height: 75px;
}
:has() 兼容性矾缓,需要一點時間

比較可惜的是怀酷,:has() 在最近的 Selectors Level 4 規(guī)范中被確定,目前的兼容性還比較慘淡


QQ截圖20231206112133.png

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

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子样眠,更是在濱河造成了極大的恐慌竞滓,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吹缔,死亡現(xiàn)場離奇詭異商佑,居然都是意外死亡,警方通過查閱死者的電腦和手機厢塘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門茶没,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晚碾,你說我怎么就攤上這事抓半。” “怎么了格嘁?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵笛求,是天一觀的道長。 經(jīng)常有香客問我糕簿,道長探入,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任懂诗,我火速辦了婚禮蜂嗽,結果婚禮上,老公的妹妹穿的比我還像新娘殃恒。我一直安慰自己植旧,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布离唐。 她就那樣靜靜地躺著病附,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亥鬓。 梳的紋絲不亂的頭發(fā)上完沪,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音贮竟,去河邊找鬼丽焊。 笑死,一個胖子當著我的面吹牛咕别,可吹牛的內(nèi)容都是我干的技健。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼惰拱,長吁一口氣:“原來是場噩夢啊……” “哼雌贱!你這毒婦竟也來了啊送?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤欣孤,失蹤者是張志新(化名)和其女友劉穎馋没,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體降传,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡篷朵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了婆排。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片声旺。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖段只,靈堂內(nèi)的尸體忽然破棺而出腮猖,到底是詐尸還是另有隱情,我是刑警寧澤赞枕,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布澈缺,位于F島的核電站,受9級特大地震影響炕婶,放射性物質(zhì)發(fā)生泄漏姐赡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一古话、第九天 我趴在偏房一處隱蔽的房頂上張望雏吭。 院中可真熱鬧锁施,春花似錦陪踩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姥饰,卻和暖如春傻谁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背列粪。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工审磁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岂座。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓态蒂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親费什。 傳聞我的和親對象是個殘疾皇子钾恢,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容