共计 8368 个字符,预计需要花费 21 分钟才能阅读完成。
在 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
急躁期待,给给工夫一点工夫,这么好的选择器马上就能大规模利用了。
最初
本文到此结束,心愿对你有帮忙 :)
想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 — iCSS 前端趣闻 😄
<img width=160 src=”https://raw.githubusercontent.com/chokcoco/chokcoco/main/gzh_style.png”>
更多精彩 CSS 技术文章汇总在我的 Github — iCSS,继续更新,欢送点个 star 订阅珍藏。
如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。