深入理解 CSS(Cascading Style Sheets)中的层叠(Cascading)
标题中的 cascading 亦可以理解为级联。
进入正文,这是一个很有意思的现象。可以直接跳到 总结一下 部分,看完再回过头来阅读本文。
引子
假设我们有如下结构:
<p class="txt" style="color:red">123456789</p>
上面的 p
标签只有一个内联 css,很明显,在没有其他样式的干预下,文本 .txt
的颜色肯定就是红色的。
如果此时,我们希望改变 .txt
p 标签元素的内容文字的颜色,但是不能去修改内联 css,只能通过样式文件去实现,像是这样:
.txt {
color: green;
}
嗯。稍微对 css 有点了解的同学都会知道,上面的 css 文件设置的样式不会生效,因为内联样式比上述 css 中的样式优先级要更高。
上述这种说法不是很严谨,下文会细说。
ok,有同学就会说了,这简单,在 css 样式文件中添加 !important
后缀即可 。像是这样:
.txt {
color: green!important;
}
如此操作之后,文本的颜色确实变成了绿色,因为在 css 文件中带 !important
后缀的规则优先级大于内联样式中同个但不带 !important
的样式。
内联样式的 !important 与样式表中的 !important
问题来了。
如果在内联样式中,我们也给加上 !important
会怎么样呢?
<p class="txt" style="color:red!important">123456789</p>
.txt {
color: green!important;
}
此时,内联的 !important
优先级更高,文本表现为红色。
问题又来了,那如果此时我们无法修改内联样式,只能修改样式表,有办法能覆盖内掉内联的 !important
吗?
animtion 的威力(chromium 内核)
哦吼,还真有一种看似是奇技淫巧,实则不是的方法。让我们康康:
<p class="txt" style="color:red!important">123456789</p>
我们给 .txt
p 元素新增一个动画,改变它的颜色。
.txt {
animation: colorgreen 2s infinite;
}
@keyframes colorgreen {
0%,
100% {
color: green;
}
}
这里新增了一个无限循环的动画,且动画初始状态及结束状态都赋予 color: green
。甚至,我们都没有在规则后缀添加 !important
。
神奇的事情发生了,文本的颜色变成了绿色,成功的覆盖了内联的 <p class="txt" style="color:red!important">
的红色样式。
codepen demo -- the priority of css animation
常见 css 优先级误区
严格来说也不算是误区(错误),但是这种说法不够严谨。
通常我们聊到 css 规则的优先级,第一时间都会想到这个表,也就是给不同的 css 规则赋予不同的权重:
一个选择器的优先级可以说是由四个部分相加 (分量),可以认为是 个十百千 四位数的四个位数:
- 千位: 如果声明在 style 的属性(内联样式)则该位得一分。这样的声明没有选择器,所以它得分总是1000
- 百位: 选择器中包含id选择器则该位得一分
- 十位: 选择器中包含类选择器、属性选择器或者伪类则该位得一分
- 个位:选择器中包含元素、伪元素选择器则该位得一分
总的来说是规则是:
内联 > id 选择器 > 类/属性/伪类选择器 > 标签元素/伪元素
上面的规则没有问题的。但是,注意,这里仅仅考虑的是页面作者定义的样式的优先级。首先,它并且没有包含 !important
规则。
其次,对于决定一个 css 样式的最终表现而言,还有非常重要的另外一个概念 -- 层叠。
cascading -- 层叠
层叠是 css 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在css处于核心地位,css的全称层叠样式表正是强调了这一点。
那么什么所谓的多个源又表示什么呢?下面是影响层叠的五个源:
-
浏览器会有一个基本的样式表来给任何网页设置默认样式。这些样式统称用户代理样式
-
网页的作者可以定义文档的样式,这是最常见的样式表。大多数情况下此类型样式表会定义多个,它们构成网站的视觉和体验,即页面主题,可以理解为页面作者样式
-
读者,作为浏览器的用户,可以使用自定义样式表定制使用体验,可以理解为用户样式
-
动画(animation),指使用 @keyframes @规则定义状态间的动画,动画序列中定义关键帧的样式来控制css动画序列
-
过渡 (transition)
css动画与层叠(重点)
css动画,指使用@keyframes @规则定义状态间的动画。
这里有个重点:关键帧不参与层叠。
这意味着在任何时候 css 都是取单一的 @keyframes 的值而不会是某几个@keyframe的混合。同时仍应注意用 @keyframes(@规则)定义的值会覆盖全部普通值,但会被 !important 的值覆盖
这里我其实没弄很明白,这里的意思就是动画过程中的每一帧,决定元素的样式表现只取决于单一的 @keyframes 的值,但是规范和 mdn 文档中都明确表明,动画 @keyframes 中的值仍会被 !important 规则覆盖,但是实际测试结果,在 chromium 内核下,动画 @keyframes 中的值层叠顺序高于 !important 规则。
css 选择器的层叠(级联)顺序
上面说的常见的优先级误区,仅仅是规定了网页的作者定义的样式的优先级。除此之外,css 优先级还需要考虑选择器的层叠(级联)顺序。
只有在层叠顺序相等时,使用哪个值才取决于样式的优先级。
根据 css cascading 4 最新标准:
定义的当前规范下申明的层叠顺序优先级如下(越往下的优先级越高,下面的规则按升序排列):
- normal user agent declarations
- normal user declarations
- normal author declarations
- animation declarations
- important author declarations
- important user declarations
- important user agent declarations
- transition declarations
简单翻译一下:
按照上述算法,大概是这样:
过渡动画过程中每一帧的样式 > 用户代理、用户、页面作者设置的!important样式 > 动画过程中每一帧的样式优先级 > 页面作者、用户、用户代理普通样式
然而,经过多个浏览器的测试,实际上并不是这样。(尴尬了)
实际测试的结果
实际代码测试的结果得出的结论其实是与规范中的优先级不大一致的。
不同内核浏览器实际表现不大一致,
chrome 78 / safari 13.0.4 / edge 44.18362 (与规范表现不一致,chromium内核)
animation 动画样式 > 页面作者定义的 !important 样式 > transition 过渡动画中的样式 > 普通样式
firefox 71.0 (与规范表现一致)
页面作者定义的 !important 样式 > animation 动画样式 > transition 过渡动画中的样式 > 普通样式
codepen demo -- the priority of css animation
总结一下
上文其实很绕,看得人很晕。简单总结一下:
-
决定一个元素的样式的最终表现,除了需要比较页面作者定义的样式的优先级之外,还需要比较样式的层叠顺序;
-
层叠是 css 的一个基本特征,定义了如何合并来自多个源的属性值的算法,5 个决定 css 样式的源分别是:用户代理样式、页面作者样式、用户样式、动画、过渡;
-
只有在层叠顺序相等时,元素的最终样式使用哪个值才取决于样式的优先级;
-
最新规范中给出的层叠顺序优先级与实际测得的有出入,不同内核浏览器实际表现不一致。
更多详细的关于层叠和样式优先级的概念,你可以看看下面:
上述 mdn 的两份文档都是有中文版的,但是发现其中中文版有部分与英文版规范不一致,应该是后面英文版有更新,但是没有同步到中文版,遇到这种情况还是应该去读读规范,并且自己实际动手实验一下。
最后
上面的第四点是我自己实测所得,可能是我搞错了,或者是我理解错了,如果是我的错误希望大家帮忙指出,共同进步学习。
更多精彩 css 技术文章汇总在我的 github -- icss ,持续更新,欢迎点个 star 订阅收藏。
好了,本文到此结束,希望对你有帮助 :)
如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。