浅谈逻辑选择器 Is、Where、Not、Has

网站建设4年前发布
46 00

在 CSS 选择器家族中,新增这样一类比较新的选择器 — 逻辑选择器,目前共有 4 名成员:,本文将带领大家了解、深入它们。做到学以致用,写出更现代化的选择器。,:is() CSS伪类函数将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。,在之前,对于多个不同父容器的同个子元素的一些共性样式设置,可能会出现如下 CSS 代码:,而如今有了 :is() 伪类,上述代码可以改写成:,它并没有实现某种选择器的新功能,更像是一种语法糖,类似于 JavaScript ES6 中的 Class() 语法,只是对原有功能的重新封装设计,实现了更容易的表达一个操作的语法,简化了某些复杂代码的写法。,一图胜前言(引用至 New CSS functional pseudo-class selectors :is() and :where()[1]):,再来看看这种情况,原本的 CSS 代码如下:,如果要将上述 HTML 中,<div><p> 下的<span><i>的 color 设置为 red,正常的 CSS 可能是这样:,有了 :is() 后,代码可以简化为:,结果如下:,这里,也支持 :is() 的层叠连用。通过 :is(div, p) :is(span, i) 的排列组合,可以组合出上述 4 行的选择器,达到同样的效果。,当然,这个例子比较简单,看不出 :is() 的威力。下面这个例子就比较明显,这么一大段 CSS 选择器代码:,可以利用 :is() 优化为:,有个特例,不能用 :is() 来选取 ::before 和 ::after 两个伪元素。譬如:,不能写成:,看这样一种有意思的情况:,我们给带有 .test-class 的元素,设置一个默认的颜色:,如果,这个时候,我们引入 :is() 进行匹配:,此时,由于 div :is(p) 可以看成 div p,优先级是没有 div .test-class 高的,因此,被选中的文本的颜色是不会发生变化的。,但是,如果,我们在 :is() 选择器中,加上一个 #test-id,情况就不一样了。,按照理解,如果把上述选择器拆分,上述代码可以拆分成:,那么,我们有理由猜想,带有 #text-id 的。<p>元素由于有了更高优先级的选择器,颜色将会变成 blue,而另外一个 div p 由于优先级不够高的问题,导致第一段文本依旧是 green。,但是,这里,神奇的是,两段文本都变成了 blue:,CodePen Demo — the specificity of CSS :is selector[2]。,这是由于,:is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。我们不能把它们割裂开来看。,对于 div :is(p, #text-id),is:() 内部有一个 id 选择器,因此,被该条规则匹配中的元素,全部都会应用 div #id 这一级别的选择器优先级。这里非常重要,再强调一下,对于 :is() 选择器的优先级,我们不能把它们割裂开来看,它们是一个整体,优先级取决于选择器列表中优先级最高的选择器。,:is() 是最新的规范命名,在之前,有过有同样功能的选择,分别是:,当然,下面 3 个都已经废弃,不建议再继续使用。而到今天(2022-04-27):is() 的兼容性已经非常不错了,不需要兼容 IE 系列的话可以考虑开始用起来(配合 autoprefixer),看看 CanIUse[3]:,了解了 :is 后,我们可以再来看看 :where,它们两个有着非常强的关联性。:where 同样是将选择器列表作为其参数,并选择可以由该列表中的选择器之一选择的任何元素。,还是这个例子:,上述的代码使用了 :where,可以近似的看为:,这就有意思了,这不是和上面说的 :is 一样了么?,那么它们的区别在什么地方呢?,首先,从语法上,:is 和 :where 是一模一样的。它们的核心区别点在于 优先级。,来看这样一个例子:,CSS 代码如下:,正常按我们的理解而言,:is(div) p 和 :where(div) p 都可以转化为 div p,由于 :where(div) p 后定义,所以文字的颜色,应该是 green 绿色,但是,实际的颜色表现为 color: red 红色:,这是因为,:where() 和 :is() 的不同之处在于,:where() 的优先级总是为 0 ,但是 :is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。,上述的例子还不是特别明显,我们再稍微改造下:,我们给 div 添加上一个 id 属性,改造上述 CSS 代码:,即便如此,由于 :where(#container) 的优先级为 0,因此文字的颜色,依旧为红色 red。:where() 的优先级总是为 0 这一点在使用的过程中需要牢记。,CSS 选择器的一个非常大的特点就在于组合嵌套。:is 和 :where 也不例外,因此,它们也可以互相组合嵌套使用,下述的 CSS 选择器都是合理的:,这里简单总结下,:is 和 :where 都是非常好的分组逻辑选择器,唯一的区别在于:where() 的优先级总是为 0,而:is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。,下面我们介绍一下非常有用的 :not 伪类选择器。,:not 伪类选择器用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中,它也被称为反选伪类(negation pseudo-class)。,举个例子,HTML 结构如下:,
,div:not(.b) 它可以选择除了 class 为 .b 元素之外的所有 div 元素:,有趣的是,在 MDN 介绍 :not 的页面,有这样一个例子:,意思是,:not(p) 可以选择任何不是<p> 标签的元素。然而,上面的 CSS 选择器,在如下的 HTML 结构,实测的结果不太对劲。,结果如下:,意思是,:not(p) 仍然可以选中<p> 元素。我尝试了多个浏览器,得到的效果都是一致的。,CodePen Demo — :not pesudo demo[4]。,这是为什么呢?这是由于 :not(p) 同样能够选中 ,那么 的 color 即变成了 blue,由于 color 是一个可继承属性,,标签继承了 的 color 属性,导致看到的也是蓝色。,我们把它改成一个不可继承的属性,试试看:,OK,这次<p> 没有边框体现,没有问题!实际使用的时候,需要注意这一层继承的问题!,下面是一些使用 :not 需要注意的问题。,:not、:is、:where 这几个伪类不像其它伪类,它不会增加选择器的优先级。它的优先级即为它参数选择器的优先级。,并且,在 CSS Selectors Level 3[5],:not() 内只支持单个选择器,而从 CSS Selectors Level 4[6] 开始,:not() 内部支持多个选择器,像是这样:,与 :is() 类似,:not() 选择器本身不会影响选择器的优先级,它的优先级是由它的选择器列表中优先级最高的选择器决定的。,使用 :not(*) 将匹配任何非元素的元素,因此这个规则将永远不会被应用。,相当于一段没有任何意义的代码。,禁止套娃。:not 伪类不允许嵌套,这意味着 :not(:not(…)) 是无效的。,那么,:not() 有什么特别有意思的应用场景呢?我这里列举一个。,在 W3 CSS selectors-4 规范[7] 中,新增了一个非常有意思的 :focus-visible 伪类。,:focus-visible 这个选择器可以有效地根据用户的输入方式(鼠标 vs 键盘)展示不同形式的焦点。,有了这个伪类,就可以做到,当用户使用鼠标操作可聚焦元素时,不展示 :focus 样式或者让其表现较弱,而当用户使用键盘操作焦点时,利用 :focus-visible,让可获焦元素获得一个较强的表现样式。,看个简单的 Demo:,使用鼠标点击:,可以看到,使用鼠标点击的时候,触发了元素的 :active 伪类,也触发了 :focus伪类,不太美观。但是如果设置了 outline: none 又会使键盘用户的体验非常糟糕。因为当键盘用户使用 Tab 尝试切换焦点的时候,会因为 outline: none 而无所适从。,因此,可以使用 :focus-visible 伪类改造一下:,看看效果,分别是在鼠标点击 Button 和使用键盘控制焦点点击 Button:,CodePen Demo — :focus-visible example[8]。,可以看到,使用鼠标点击,不会触发 :foucs,只有当键盘操作聚焦元素,使用 Tab 切换焦点时,outline: 2px solid red 这段代码才会生效。,这样,我们就既保证了正常用户的点击体验,也保证了无法使用鼠标的用户的焦点管理体验,在可访问性方面下了功夫。,值得注意的是,这里为什么使用了 button:focus:not(:focus-visible) 这么绕的写法而不是直接这样写呢:,解释一下,button:focus:not(:focus-visible) 的意思是,button 元素触发 focus 状态,并且不是通过 focus-visible 触发,理解过来就是在支持 :focus-visible 的浏览器,通过鼠标激活 :focus 的 button 元素,这种情况下,不需要设置 outline。,为的是兼容不支持 :focus-visible 的浏览器,当 :focus-visible 不兼容时,还是需要有 :focus 伪类的存在。,因此,这里借助 :not() 伪类,巧妙的实现了一个实用效果的方案降级。,经历了 CSS Selectors Level 3 & CSS Selectors Level 4 两个版本,到今天(2020-05-04),除去 IE 系列,:not 的兼容性已经非常之好了:,OK。最后到所有逻辑选择器里面最重磅的 :has 出场了。它之所以重要是因为它的诞生,填补了在之前 CSS 选择器中,没有核心意义上真正的父选择器的空缺。,:has 伪类接受一个选择器组作为参数,该参数相对于该元素的 :scope[9] 至少匹配一个元素。,实际看个例子:,我们通过 div:has(.g-test-has) 选择器,意思是,选择 div 下存在 class 为 .g-test-has 的 div 元素。,注意,这里选择的不是 :has() 内包裹的选择器选中的元素,而是使用 :has() 伪类的宿主元素。,效果如下:,可以看到,由于第二个 div 下存在 class 为 .g-test-has 的元素,因此第二个 div 被加上了 border。,我们再通过几个 DEMO 加深下印象。:has() 内还可以写的更为复杂一点。,这里,要求准确选择 div 下直接子元素是 h2,且 h2 下直接子元素有 span 的 div 元素。注意,选择的最上层使用 :has() 的父元素 div。结果如下:,这里体现的是嵌套结构,精确寻找对应的父元素。,还有一种情况,在之前也比较难处理,同级结构的兄元素选择。,看这个 DEMO:,
,我们想找到兄弟层级关系中,后面接了<h2>元素的  .has-test 元素,可以这样写:,效果如下:,这里体现的是兄弟结构,精确寻找对应的前置兄元素。,这样,一直以来,CSS 没有实现的父选择器,借由 :has() 开始,也能够做到了。这个选择器,能够极大程度的提升开发体验,解决之前需要比较多 JavaScript 代码才能够完成的事。,上述 DEMO 汇总,你可以戳这里 CodePen Demo — :has Demo[10]。,比较可惜的是,:has() 在最近的 Selectors Level 4[11] 规范中被确定,目前的兼容性还比较惨淡,截止至 2022-05-04,Safari 和 最新版的 Chrome(V101,可通过开启 Experimental Web Platform features 体验)。,耐心等待,给给时间一点时间,这么好的选择器马上就能大规模应用了。,本文到此结束,希望对你有帮助 :),
,

© 版权声明

相关文章