一、一个看似简单的问题
你在 CSS 里写了 color: red;,浏览器就显示红色。看起来很简单,对吧?
但请思考以下场景:
- 同一个元素,被多个选择器选中,且它们都声明了
color。浏览器选哪个? - 你没给某个元素写任何
color声明,但它仍然有颜色——从哪来的? - 你写了
width: 50%,浏览器怎么知道最终是多少像素? - 为什么有些属性(如
color)会自动“传染”给子元素,而有些(如border)不会?
这些问题的答案,都藏在 CSS 最核心的机制里:浏览器确定一个属性最终取值的完整流程。上一篇我们学了选择器(怎么选中元素),这一篇我们要学——选中之后,属性值到底怎么定。
把这个流程吃透,你将获得一种“预测能力”:不需要打开浏览器,就能推断出任何一个元素、任何一个属性最终会呈现什么样式。这是从“会写 CSS”到“真正理解 CSS”的关键一步。
二、从声明到最终值:一条流水线
浏览器处理一个 CSS 属性值,要经过一条严格的流水线。W3C 规范将其分解为多个阶段,我们可以简化为以下四步:
- 收集所有声明:找出所有对这个元素有效的 CSS 声明(来自不同样式表、不同选择器)。
- 层叠决定胜出者:根据重要性和优先级,从冲突的声明中选出一个作为“指定值”。
- 继承与默认值兜底:如果没有任何声明,尝试从父元素继承;如果也不能继承,使用属性的初始值。得到“计算值”。
- 转化为实际像素:将相对单位、百分比等转化为屏幕上的绝对像素,得到“使用值”和“实际值”。
下面我们一步步拆解。
三、第一步:所有声明从哪来
浏览器会从五个来源收集对一个元素有效的 CSS 声明:
| 来源 | 说明 | 示例 |
|---|---|---|
| 作者样式表 | 你写的 CSS(内联、内部、外部) | .box { color: red; } |
| 用户样式表 | 用户在浏览器设置中自定义的样式(极少见) | 用户强制所有段落用大号字体 |
| 用户代理样式表 | 浏览器自带的默认样式 | <h1> 默认加粗、大字号 |
| 动画声明 | CSS 动画(@keyframes)正在作用的值 |
动画中的 opacity 过渡值 |
| 过渡声明 | CSS 过渡(transition)正在变化的值 |
从蓝色平滑过渡到红色时的中间色 |
其中作者样式表是我们最常打交道的来源。而用户代理样式表解释了为什么即使你一行 CSS 都不写,页面也不是白底黑字的纯文本——浏览器给 <h1> 加了字号和加粗,给 <a> 加了蓝色和下划线,给 <ul> 加了左边距。
动画和过渡声明的优先级特殊,我们在动画篇再详谈。现在先聚焦于最核心的层叠规则。
四、第二步:层叠——当多个声明冲突时
“层叠”(Cascading)是 CSS 名字的来源,也是它最重要的机制。当多个来源、多个选择器都对同一个属性做了声明,浏览器按以下顺序决定胜出者:
第一层:按重要性和来源排序
所有声明先被分为两类:普通声明和带 !important 的声明。然后按来源排列优先级(从低到高):
- 用户代理样式表的普通声明(浏览器默认样式)
- 用户样式表的普通声明(用户在浏览器里自定义的样式)
- 作者样式表的普通声明(你写的 CSS)
- CSS 动画中的声明(即使不是
!important) - 作者样式表的
!important声明 - 用户样式表的
!important声明 - 用户代理样式表的
!important声明
关键认知:用户的 !important 可以覆盖作者的 !important,这是浏览器给最终用户保留的最高控制权(例如强制放大字体)。在正常开发中,第 3 和第 5 条最重要:你写的 CSS 覆盖浏览器默认样式,而你写的 !important 又覆盖你自己写的普通样式。
第二层:按选择器优先级(specificity)排序
在同一个来源、同一重要性级别内部,选择器优先级高的胜出。这就是上一篇详讲的 (a, b, c, d) 规则:
- 内联样式:a=1
- ID 选择器:b=1
- 类/属性/伪类:c=1
- 标签/伪元素:d=1
第三层:按书写顺序排序
如果优先级完全相同,后写的覆盖先写的。这就是“层叠”最朴素的含义——样式表是一层一层叠加上去的,后面的盖住前面的。
一个完整的冲突解决示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>层叠演示</title>
<style>
p { color: blue; } /* ① 优先级 (0,0,0,1) */
.text { color: green; } /* ② 优先级 (0,0,1,1) */
#intro { color: orange; } /* ③ 优先级 (0,1,0,1) */
p { color: purple; } /* ④ 优先级 (0,0,0,1),在②③之后,但不影响 */
</style>
</head>
<body>
<p id="intro" class="text">我是什么颜色?</p>
</body>
</html>
最终颜色是 橙色。因为 ID 选择器 #intro 的优先级最高,尽管它在书写顺序上不是最后的。优先级先于顺序,顺序只在优先级相同时起作用。
五、第三步:继承——从父元素“传染”的值
如果经过层叠,某个属性仍然没有找到任何声明,浏览器就会尝试继承:从父元素那里把这个属性的值“抄过来”。
哪些属性会自动继承
并非所有属性都会继承。W3C 规范对每个属性都标注了“是否继承”。一般来说:
- 文本相关属性大多可继承:
color、font-family、font-size、font-weight、line-height、text-align、visibility。 - 盒模型相关属性通常不继承:
width、height、margin、padding、border、background。
这符合直觉:给 <body> 设置 color: blue,整个页面的文字都变蓝(继承)。给 <body> 设置 border: 1px solid red,只有 <body> 自己有边框,内部元素不会自动获得边框(不继承)。
强制继承:inherit 关键字
对于默认不继承的属性,可以手动指定让它继承:
.child {
border: inherit; /* 强制从父元素继承 border 的值 */
}
阻止继承:initial 关键字
对于默认会继承的属性,也可以阻止它继承,直接使用该属性的初始值:
.child {
color: initial; /* 使用 color 的初始值(通常是黑色),不继承父元素的颜色 */
}
继承的优先级最低
继承来的值优先级比任何直接声明都低。即使是一个优先级为 (0,0,0,1) 的标签选择器,也能覆盖从父元素继承来的值。只有当一个属性没有任何直接声明时,继承才起作用。
六、第四步:初始值——每个属性的出厂设置
如果某个属性既没有层叠获胜的声明,也无法从父元素继承,浏览器就使用该属性的初始值。每个 CSS 属性在规范中都定义了一个初始值:
color的初始值是canvastext(通常表现为黑色)。background-color的初始值是transparent(透明)。display的初始值是inline。position的初始值是static。
这就是为什么即使你不写任何 CSS,文字也是黑色的(color 的初始值),背景是透明的(background-color 的初始值),<span> 不会换行(display: inline 的初始值)。
注意:初始值不等于浏览器默认样式。浏览器默认样式(用户代理样式表)已经覆盖了初始值。例如 <h1> 的 font-size 初始值是 medium,但浏览器默认样式把它设为了 2em。如果你用 initial 关键字,回到的是规范定义的初始值,不是浏览器默认值。
七、从“指定值”到“实际值”:数值的转化旅程
层叠选出的胜出值,称为指定值(specified value)。但一个属性值在真正渲染到屏幕上之前,还要经历多次转化。
计算值(computed value)
指定值中的相对单位(如 em、%)被换算为绝对值(如 px)。例如:
font-size: 1.5em,如果父元素字号为 16px,则计算值为 24px。width: 80%,此时不计算,因为百分比需要等到确定包含块的宽度后才能转化。所以计算值保留为80%。
使用值(used value)
在完成整个文档的布局计算后,所有百分比被转化为绝对像素。例如 width: 80% 在包含块宽度为 1000px 时,使用值为 800px。
实际值(actual value)
某些情况下,使用值可能因为硬件限制而无法精确实现(如最小像素精度),浏览器会四舍五入到最接近的可实现值。这通常对开发者透明,但在处理极细微尺寸或高 DPI 设备时可能有细微影响。
转化示意
指定值 (specified) → 计算值 (computed) → 使用值 (used) → 实际值 (actual)
"1.5em" → "24px" → 24px → 24px (或 24px)
"80%" → "80%" → 800px → 800px
理解这个转化过程,能帮你解释很多“为什么百分比没生效”“为什么 em 的计算结果与预期不符”的问题。
八、综合演示:追踪一个属性值的完整旅程
下面这段代码,我们故意制造多重冲突,然后一步步追踪 color 属性最终如何确定。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>属性值确定演示</title>
<style>
/* ① 作者样式表 - 标签选择器 */
p { color: blue; }
/* ② 作者样式表 - 类选择器(优先级更高) */
.message { color: green; }
/* ③ 作者样式表 - ID 选择器(优先级更高) */
#special { color: orange; }
/* ④ 作者样式表 - 相同优先级,后写覆盖先写 */
p { color: purple; } /* 与①相同优先级,在①之后,所以覆盖① */
</style>
</head>
<body>
<p>我会继承 body 的 color 吗?不,因为我有直接声明。</p>
<p class="message">我是绿色吗?</p>
<p id="special" class="message">我是橙色吗?</p>
<div>
<p>我在 div 里,但我自己有声明,不受 div 影响。</p>
<span>我没有任何声明,会继承父元素 div 的 color。</span>
</div>
<script>
// 在控制台查看计算后的样式
console.log('打开 F12 → Elements → Styles 面板,查看每个元素的 color 属性');
</script>
</body>
</html>
逐元素分析:
- 第一个
<p>:没有 class 和 id。层叠中标签选择器p的声明有两条(①和④),④覆盖①,最终采用purple。 - 第二个
<p class="message">:类选择器.message(②)优先级 (0,0,1,1) 高于标签选择器 (0,0,0,1),采用green。 - 第三个
<p id="special" class="message">:ID 选择器#special(③)优先级 (0,1,0,1) 高于类选择器 (0,0,1,1) 和标签选择器 (0,0,0,1),采用orange。 <div>中的<p>:有直接的标签选择器声明,层叠后采用purple。不继承 div。<span>:没有任何直接声明,且color是可继承属性。它的父元素<div>也没有color的声明,继续向上找到<body>,<body>也没有声明。最终使用color的初始值,通常是黑色。
你可以打开 F12 开发者工具,在 Elements 面板中选中每个元素,查看右侧 Styles 面板。被划掉的声明就是层叠中落败的规则,最上面那条就是最终生效的指定值。
九、本篇小结
这一篇我们拆解了 CSS 属性值确定的完整流程:
- 层叠:按重要性(普通 vs !important)→ 来源(用户代理、用户、作者、动画)→ 优先级(a,b,c,d)→ 书写顺序,逐级决定胜出者。
- 继承:当没有直接声明时,文本相关属性从父元素继承,盒模型属性不继承。可用
inherit和initial关键字手动控制。 - 初始值:每个属性在规范中定义的出厂默认值,是最后一道兜底。
- 数值转化:指定值 → 计算值(相对单位换算) → 使用值(百分比转化) → 实际值(像素精度调整)。
这套机制是 CSS 的底层操作系统。理解了它,你就掌握了调试样式问题的终极框架——任何“为什么这个样式没生效”的问题,都可以沿着“层叠 → 继承 → 初始值”的路径逐一排查。
下一篇预告
下一篇,我们将进入 CSS 中最具挑战性也最强大的领域——布局。从正常流(Normal Flow)开始,逐步深入浮动(Float)、定位(Position)、弹性盒子(Flexbox)和网格(Grid)。你会明白,布局不是死记硬背一堆属性,而是理解浏览器如何根据盒模型和格式化上下文来排列元素。
前端,每周更新。













暂无评论内容