分三步定下来
一句话总结
当同一属性同时匹配多条规则时,浏览器会按重要性 → 特异性 → 顺序这三个阶段选出一条。如果在前一阶段已经分出胜负,就不会再看后面的阶段。
为什么要记住这一点
CSS 中最耗时间的时刻,往往不是因为不懂语法,而是遇到**“明明写了却不生效”**。开发者工具中明明能看到规则,却被画上了删除线。
如果不知道原因,只是把选择器再写长一点,或再叠加一条规则蒙混过去,下一个人就必须写更长的选择器,再下一个人就会使用 !important。几个月后,文件中哪些行可以删除将无人知晓。CSS 几乎总是沿着这条路径崩坏。
理解这三个阶段,就能当场回答**“如果它输了,为什么会输”**。只要分清是特异性还是顺序导致,修复方式就会完全不同。
第一阶段——重要性
사용자 !important > 작성자 !important > 작성자 일반 > 사용자 일반 > 브라우저 기본
这里由 !important 获胜,所以紧急时很容易伸手使用它。但能战胜 !important 的只有另一个 !important。一旦使用,这个属性以后就只能继续用同样方式处理。这正是 CSS 崩坏的典型路径。
当你想使用 !important 时,真正的问题通常是选择器设计。
第二阶段——特异性
用三位数字计算。
| 位 | 计算对象 | 示例 |
|---|---|---|
| a | ID | #nav |
| b | 类、属性、伪类 | .btn、[open]、:hover |
| c | 标签、伪元素 | div、::before |
a → 0,0,1
.btn.primary → 0,2,0
#nav a:hover → 1,1,1
ul > li::marker → 0,0,3
各位之间不会进位。 即使连接 11 个类,也无法胜过一个 ID,即 0,11,0 < 1,0,0。
还有两个特殊情况。
:where(...)的特异性永远是 0。:where(.card) .title=0,1,0。库要提供“易于覆盖的默认值”时会使用它:is(...)、:not(...)采用括号内特异性最高的项
第三阶段——顺序
这是最经常让人踩坑的阶段。
重要性和特异性都相同时,写在后面的规则获胜。 这里指的是文件中的位置,而不是它在页面中的语义。
@media (max-width: 900px) {
.side { position: fixed; } /* 모바일 덮개 */
}
.side { position: sticky; } /* ← 나중에 썼다 */
这段代码在移动端也会是 sticky。位于媒体查询中并不会让规则更强。由于特异性相同(两者都是 0,1,0),后写的规则获胜。
基础规则永远要放在媒体查询之前。
这是本服务中真实发生过的错误。移动端目录遮罩无法打开,原因是基础规则 .lessonSide { position: sticky } 位于 @media 块的下方。把整个块移到上方后便修复了。
继承不是层叠
color、font-family、line-height 等属性会被子元素继承。margin、padding、display、border 则不会。
继承得到的值,比任何直接作用于该元素的规则都弱。 无论直接规则的特异性多低,它都会获胜。只有“完全没有规则”时才会使用继承值。
自定义属性会继承
:root { --accent: #3b82f6; }
.btn { background: var(--accent); }
--accent 会继承,因此放在 :root 后可在任何位置使用,也能只在某个子树中覆盖。
.dark-panel { --accent: #93c5fd; } /* 이 안의 .btn 들만 색이 바뀐다 */
这正是它与预处理器变量(SCSS $)的决定性差异。SCSS 变量会在编译时消失,而自定义属性会存活到运行时,并参与层叠。这就是主题切换只需一行 JavaScript 的原因。
网格中真正容易出问题的地方
网格很容易学,但有一个知识点不知道就会造成严重问题。
设置为 display: none 的子元素会彻底退出网格。 它不会留下空位。
.layout { grid-template-columns: 0 1fr; } /* 접었을 때 목차를 0px 로 */
.layout.hidden .side { display: none; }
原意是“把目录折叠为 0px”。但由于 display: none 让目录退出布局流,正文被推进了第一列(0px)。每个字符都另起一行,页面最终高达 33,000px。
这也是本服务中真实发生过的错误。修复只需一行。
.layout.hidden { grid-template-columns: 1fr; } /* 칸을 아예 하나로 */
不要把列宽变为 0,而应减少列的数量。 子元素消失时,对应的列也必须消失。
什么时候用网格,什么时候用弹性布局
- 网格——同时确定两个方向。用于页面骨架、卡片网格
- 弹性布局——在一行(或一列)中分配空间。用于工具栏、按钮组、列表项
拿不准时可以这样问:“我是否想同时确定行和列?” 如果是,就用网格。
弹性布局中最常用的是 justify-content: space-between + align-items: center + gap 这三项。使用 margin-left: auto 把单个元素推到右侧也很常见。
现场诊断顺序
CSS “不生效”时,不要先添加规则,而应按顺序检查。
- 选择器是否真的匹配该元素——开发者工具中是否能看到规则
- 是否输给了其他规则——若有删除线,说明它输了
- 如果输了,为什么——因为特异性还是顺序
- 是否期待继承值——若有直接作用的规则,就不会使用继承
只有检查完这四项仍没有答案时,才使用 !important。通常在第三步就能结束。
实务中这套顺序之所以重要,是因为每个阶段的修复方法不同。输在特异性就应调整选择器,输在顺序就应改变文件加载顺序或规则位置。如果不区分原因而同时改动两边,就无法知道问题是真正修复了,还是只是偶然绕过去了。