前言:为什么需要 :not()?
在现代 Web 开发中,我们经常面临一种看似简单却难以优雅实现的需求:
"对一组元素应用样式,但排除其中某些特定的元素。"
例如:
- 表单中所有输入框都加边框,但禁用状态的不加;
- 导航栏中所有链接变色,但当前页面对应的链接保持原色;
- 鼠标悬停在卡片容器上时,所有子卡片旋转,唯独鼠标正下方的那个保持静止。
传统做法是"先统一设置,再单独覆盖",但这容易导致:
- 样式冗余
- 特异性(Specificity)冲突
- 逻辑分散、可维护性差
而 CSS 的 :not() 伪类(Negation Pseudo-class)正是为解决这类问题而生。它提供了一种声明式、精准、高效的"排除法"机制,是现代 CSS 架构中不可或缺的工具。
一、规范定义与演进
1.1 起源:CSS Selectors Level 3
:not() 最初在 CSS Selectors Level 3 中被标准化,其定义如下:
The negation pseudo-class,
:not(X), is a functional notation taking a simple selector as an argument.
关键点:
- 参数
X必须是一个 简单选择器(simple selector) - 简单选择器包括:类型选择器(如
div)、类选择器(.class)、ID 选择器(#id)、属性选择器([attr])、伪类(如:hover、:nth-child()) - 不支持 复合选择器(如
div p)、伪元素(如::before)
1.2 扩展:CSS Selectors Level 4
在 CSS Selectors Level 4 中,:not() 得到重大增强:
The negation pseudo-class,
:not(), accepts a selector list as an argument.
这意味着现在可以写成:
css
:not(.a, .b, :disabled)
这极大提升了表达能力,使多条件排除成为可能。
⚠️ 注意:Level 4 目前仍处于 Working Draft 状态,但主流浏览器已广泛实现。
二、语法详解
2.1 基本语法(Level 3)
css
:not(simple-selector) {
/* styles */
}
示例:
css
p:not(.intro) { color: gray; }
input:not([type="hidden"]) { display: block; }
li:not(:first-child) { margin-top: 8px; }
2.2 扩展语法(Level 4)
css
:not(selector1, selector2, ..., selectorN) {
/* styles */
}
示例:
css
button:not(.primary, :disabled, [aria-hidden="true"]) {
opacity: 0.8;
}
2.3 可接受的选择器类型
| 类型 | 是否允许 | 示例 |
|---|---|---|
| 类选择器 | ✅ | .active |
| ID 选择器 | ✅ | #header |
| 属性选择器 | ✅ | [data-role="admin"] |
| 伪类 | ✅ | :hover, :focus, :nth-child(2n) |
| 元素类型 | ✅ | div, span |
| 伪元素 | ❌ | ::before, ::after(无效) |
| 后代/子选择器 | ❌(Level 3)✅(Level 4 中作为列表项可间接使用) | div p(不能直接作为参数) |
📌 重要规则 :
:not()的参数必须是匹配单个元素的选择器,不能是匹配元素关系的组合选择器。
三、特异性(Specificity)计算
:not() 本身不增加特异性值,但其内部的选择器会参与计算。
根据 CSS Cascading and Inheritance Level 3:
The specificity of
:not()is replaced by the specificity of its argument.
示例分析
css
p:not(.highlight) { color: red; }
p→ (0,0,1).highlight→ (0,1,0)- 整体特异性 = (0,1,1)
对比:
css
p.highlight { color: blue; } /* 特异性也是 (0,1,1) */
因此,若两者同时存在,后定义的胜出。
实践建议
- 利用
:not()可以在不提高特异性的情况下实现精确排除; - 避免在
:not()内部使用高特异性选择器(如 ID),以免意外覆盖其他规则。
四、浏览器兼容性
| 浏览器 | 单参数 :not() |
多参数 :not(a, b) |
|---|---|---|
| Chrome | ✅ 1+ | ✅ 88+ |
| Firefox | ✅ 1+ | ✅ 78+ |
| Safari | ✅ 3.2+ | ✅ 14+ |
| Edge | ✅ 12+ | ✅ 88+ |
| IE | ✅ 9+(仅单参数) | ❌ 不支持 |
💡 结论:
- 若需支持 IE9+,可安全使用
:not(:hover)、:not(.class)等单参数形式;- 多参数形式适用于现代项目(2022 年后启动的项目基本可放心使用)。
五、经典实战场景
场景 1:悬停交互中的"聚焦当前项"
需求描述
当鼠标进入父容器时,所有子项执行动画(如旋转、缩放),但当前被鼠标直接悬停的子项保持原始状态。
这是 UI/UX 中常见的"弱化非焦点项"模式。
推荐实现(现代写法)
html
<div class="gallery">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
</div>
css
.gallery {
display: flex;
gap: 20px;
padding: 20px;
}
.card {
width: 100px;
height: 100px;
background: #2196F3;
color: white;
border-radius: 12px;
display: grid;
place-content: center;
transition: transform 0.3s ease;
}
/* 核心逻辑 */
.gallery:hover .card:not(:hover) {
transform: rotate(10deg) scale(0.92);
}
原理解析
.gallery:hover触发动画上下文;.card:not(:hover)精准排除当前悬停项;transition定义在基础状态,确保进出动画平滑。
兼容性说明
- 此写法在 IE9+ 中完全可用,因为
:not(:hover)是单参数形式; - 无需额外 JS 或复杂覆盖逻辑。
场景 2:表单控件的状态管理
需求
所有输入框获得焦点时显示高亮边框,但禁用(disabled)或只读(readonly)的输入框除外。
实现
css
input:not(:disabled):not(:read-only):focus {
outline: 2px solid #FF9800;
outline-offset: 2px;
}
或使用 Level 4 语法(更简洁):
css
input:not(:disabled, :read-only):focus {
outline: 2px solid #FF9800;
}
优势
- 避免为禁用控件添加无意义的 focus 样式;
- 符合无障碍(a11y)最佳实践:禁用控件不应响应交互反馈。
场景 3:导航菜单的当前页标识
需求
导航链接默认为灰色,悬停时变蓝,但当前页面对应的链接始终为黑色且不可交互。
HTML
html
<nav class="nav">
<a href="/home" class="nav-link active">首页</a>
<a href="/about" class="nav-link">关于</a>
<a href="/contact" class="nav-link">联系</a>
</nav>
CSS
css
.nav-link {
color: #666;
text-decoration: none;
padding: 8px 12px;
transition: color 0.2s;
}
/* 排除 .active 的链接才响应 hover */
.nav-link:not(.active):hover {
color: #1976D2;
}
/* .active 链接固定为黑色 */
.nav-link.active {
color: #000;
pointer-events: none; /* 可选:禁止点击 */
}
设计价值
- 用户清晰识别当前位置;
- 避免"当前页还能点击"的反模式。
场景 4:动态排除多个静态条件
需求
在数据列表中,当用户批量操作时,所有行高亮,但以下行除外:
- 已选中的行(
.selected) - 系统保留行(
.system) - 第一行(标题行,
:first-child)
实现(Level 4)
css
.table:hover tr:not(.selected, .system, :first-child) {
background-color: #FFF3E0;
}
兼容写法(Level 3)
css
.table:hover tr {
background-color: #FFF3E0;
}
.table:hover tr.selected,
.table:hover tr.system,
.table:hover tr:first-child {
background-color: transparent;
}
⚠️ 注意:覆盖法需确保 reset 规则的特异性足够高。
六、常见误区与陷阱
误区 1:认为 :not() 能否定任意选择器
❌ 错误示例:
css
/* 无效!后代选择器不是简单选择器 */
div:not(.container .item) { ... }
/* 无效!伪元素不能用于 :not() */
p:not(::first-line) { ... }
✅ 正确做法:
- 将逻辑拆解为多个
:not()或使用其他选择器组合。
误区 2:忽略 :hover 的冒泡行为
:hover 会作用于元素及其所有祖先。因此:
html
<div class="card">
<button>Click me</button>
</div>
当鼠标悬停在 <button> 上时,.card:hover 依然为真。
这意味着 .card:not(:hover) 不会匹配,符合预期。
✅ 这是标准行为,无需额外处理。
误区 3:在 :not() 中使用高特异性选择器导致冲突
css
/* 危险:ID 选择器特异性过高 */
div:not(#special) { color: red; }
/* 可能意外覆盖其他规则 */
✅ 建议:尽量使用类或属性选择器作为 :not() 参数。
七、性能与可维护性考量
性能
:not()的解析成本略高于普通选择器,但现代浏览器优化良好;- 避免在高频渲染区域(如滚动列表)中过度嵌套
:not(); - 不要写
*:not(.x),应明确限定元素类型(如div:not(.x))。
可读性
-
使用
:not()能显著提升代码语义:css/* 清晰表达意图 */ .list-item:not(:last-child) { margin-bottom: 16px; }优于:
css.list-item { margin-bottom: 16px; } .list-item:last-child { margin-bottom: 0; }
可维护性
- 所有排除逻辑集中在一个地方;
- 新增排除条件只需扩展
:not()列表(Level 4); - 减少样式覆盖带来的"CSS 战争"。
八、与其他选择器的组合技巧
8.1 与 :is() / :where() 结合
css
/* 使用 :where() 降低特异性 */
.parent:hover :where(.child):not(:hover) {
transform: scale(0.95);
}
8.2 与属性选择器结合
css
/* 排除没有 href 的 a 标签 */
a:not([href]) {
color: gray;
cursor: default;
}
8.3 与表单伪类结合
css
/* 未填写且无效的输入框标红 */
input:not(:placeholder-shown):not(:valid) {
border-color: #f44336;
}
九、总结:何时使用 :not()?
| 场景 | 推荐度 | 说明 |
|---|---|---|
排除当前交互状态(如 :hover) |
⭐⭐⭐⭐⭐ | 最佳实践 |
| 排除特定类/属性/状态 | ⭐⭐⭐⭐☆ | 语义清晰 |
| 多条件静态排除(现代浏览器) | ⭐⭐⭐⭐⭐ | Level 4 强大 |
| 需要支持 IE 且多条件 | ⭐⭐☆☆☆ | 需降级为覆盖法 |
| 否定复杂结构选择器 | ⭐☆☆☆☆ | 不可行,需重构逻辑 |