首页 > 编程笔记

CSS :not()伪类用法详解

:not()是否定伪类,如果当前元素与括号里的选择器不匹配,则该伪类会进行匹配。例如:
:not(p) {}
会匹配所有标签不是p的元素,包括<html>元素和<body>元素。

:not() 伪类其他相关细节如下:
1) :not() 伪类的优先级是 0,即最终选择器的优先级是由括号里的表达式决定的。

例如,:not(p) {}的优先级就是 p 选择器的优先级。

2) :not() 伪类可以不断级联。例如:
input:not(:disabled):not(:read-only) {}
表示匹配所有不处于禁用状态也不处于只读状态的 <input> 元素。

3) 从 2021 年开始,所有现代浏览器均已支持在 :not() 伪类中使用多个表达式,例如下面这种写法是合法的:
/* 现代浏览器均支持 */
.cs-li:not(li, dd) {}
但是在过去,浏览器是无法解析上述用法的,需要使用下面这种冗长的写法代替:
.cs-li:not(li):not(dd) {}

在过去,下面几种写法也不支持,但是现在没有这个限制了,只要项目无须兼容 IE 浏览器,就可以放心使用。
/* 过去不支持,现在均支持 */
input:not(:disabled:read-only) {}
input:not(p:read-only) {}
input:not([id][title]) {}

此外,:not() 伪类的参数值不仅可以是选择器,还支持选择符。例如下面的语句也是可以被现代浏览器解析的:
/* 现代浏览器均支持 */
input:not(.a > .b) { border: red solid; }

:not()伪类的实际应用

:not() 伪类的最大用处就是可以优化过去我们重置 CSS 样式的策略。由于重置样式在 Web 开发中非常常见,因此 :not() 伪类的适用场景非常广泛。

例如,我们在实现选项卡切换效果的时候会默认隐藏部分选项卡面板,点击选项卡按钮后通过添加激活状态类名使隐藏的面板再次显示,CSS 代码如下:
.cs-panel {
   display: none;
}
.cs-panel.active {
   display: block;
}
实际上,这种效果有更好的实现方式,那就是使用 :not() 伪类,推荐使用下面的 CSS 代码:
.cs-panel:not(.active) {
   display: none;
}

使用 :not() 伪类有如下优点。
仍采用上面的例子,由于不同的选项卡面板里的内容不同,因此所采用的布局也不一样。假设 HTML 代码如下:
<div class="cs-panel">面板1</div>
<div class="cs-panel cs-flex">面板2</div>
<div class="cs-panel cs-grid">面板3</div>
“面板2”需要使用 Flex 布局,“面板3”需要使用 Grid 布局,结果发现传统实现的 CSS 代码对此无能为力,因为被更高优先级的 CSS 代码 .cs-panel.active 强制限定为了 display:block:
.cs-panel {
   display: none;
}
.cs-panel.active {
   display: block;
}
/*
  下面两个布局样式都无效
  .cs-panel.active的优先级更高
*/
.cs-flex {
   display: flex;
}
.cs-grid {
   display: grid;
}
但是,如果使用的是 :not() 伪类,这样的效果实现起来就很轻松:
.cs-panel:not(.active) {
   display: none;
}
/* 下面两个布局样式均有效*/
.cs-flex {
   display: flex;
}
.cs-grid {
   display: grid;
}

再比如,列表边缘对齐不应该使用下面的写法:
.cs-li {
   float: left;
   width: calc((100% - 40px) / 5);
   margin-right: 10px;
}
/* 不推荐这样重置 */
.cs-li:nth-of-type(5n) {
   margin-right: 0;
}
而应该使用 :not() 伪类:
.cs-li {
   float: left;
   width: calc((100% - 40px) / 5);
}
/* 推荐这样设置 */
.cs-li:not(:nth-of-type(5n)) {
   margin-right: 10px;
}

再如按钮样式的控制,对于禁用按钮不能有 :hover 样式,传统实现如下:
.cs-button,
.cs-button:disabled:hover {
   background-color: #fff;
}
.cs-button:hover {
   background-color: #eee;
}
如果像下面这样实现:
.cs-button {
   background-color: #fff;
}
.cs-button:not(:disabled):hover {
   background-color: #eee;
}
则代码更清晰、简洁。

总之,大家一定要培养这样的意识,一旦遇到需要重置 CSS 样式的场景,第一反应就是使用 :not() 伪类。

但是,对于某类重置场景,如果 :not() 伪类使用不当,可能会有预料之外的情况出现。

例如,对于一些阅读类的网站,希望 <article> 元素内的 <ol>、<ul> 元素依然保留默认的样式,不希望被重置。传统的实现一般是外部 CSS 重置,在 <article> 元素里再还原,CSS 示意代码如下:
ol, ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}
article ol,
article ul {
  all: revert;
}
有些读者学了本节的内容后会想到使用 :not() 伪类来实现,然后使用了如下的 CSS 代码:
:not(article) ol,
:not(article) ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}
大家看看这种实现有没有问题?

乍一看这似乎是一个很棒的实现,因为从语法上直译就是非 article 标签下的 <ol>、<ul> 元素样式全部重置。但实际上这是有问题的。例如,有如下 HTML 代码:
<article>
  <div>
    <ol>
      <li>内容1</li>
      <li>内容2</li>
      <li>内容3</li>
    </ol>
</div>
</article>
这里的 <ol> 元素的 margin 和 padding 等 CSS 属性样式理论上不应该被重置,但实际上这些样式都被重置了,因为 <ol> 元素外面的 <div> 元素也匹配 :not(article) ol 选择器。

所以,对于这种场景,:not() 伪类的使用并没有想象的那么简单,不过也不是不能实现,而是需要使用 :not() 伪类在 CSS 选择器 Level4 规范中的新语法,也就是使用选择符:
ol:not(article ol),
ul:not(article ul) {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

推荐阅读