CSS
浏览器

CSS 求值过程

约 18 分钟

前言

在字节青训营第一天的 HTML+CSS 的课程中,虽然内容很基础,但是老师还拓展讲解了很多深入的知识,很多是以前从来没注意或者学习的时候因为知识不够所忽略的。趁着本次学习的机会,整理搜集回顾自己的所学所思。

什么是 CSS 求值过程

浏览器要渲染 HTML 页面中的某个元素,首先必须要能够确切地知道这个元素每一个属性的准确值(非 em,百分比等单位)。而浏览器中 HTML 里的元素样式是如何被计算出来最终确定的,这个过程就是 CSS 的求值过程。

过程图

QQ截图20220724120005.png

预先工作

浏览器解析 HTML 页面的 DOM 树,并加载所有的样式规则

确定声明值(Declared Values)

什么是声明值

为每个属性显式地声明的值(或者说设置的值),称之为声明值(declared value),或称之为设置值。

声明值可能是程序员手动显式声明的值(包括内联样式、外部样式、行间样式中所声明的值),也可能是浏览器显式设置的浏览器自带值。

声明值是相对于默认值而言的,如果一个CSS属性在各级样式(包括浏览器自带样式、内联样式、外部样式、行间样式)中都没有显式的声明值,则使用隐式的默认值。

执行流程

在该流程中,浏览器先经过 filtering 阶段,对页面中每一个元素筛选找到匹配的所有当前有效属性值,即例如设置的无效属性值,media 媒体查询中不符合条件的样式等部分不会筛选出来。

筛选结束后,会获得该元素所有声明的属性值(包括浏览器默认样式表)。

当前阶段会确定所有筛选出来有声明确切值,且没有样式冲突的属性的结果,同时保留拥有多个声明值的属性不做处理留待后续流程解决。

例如对于以下用户样式表

p {
    font-size: 16px;
}
p.text {
    font-size: 1.2em;
}

对于 <p class="text"></p> 元素来说,由于两个属性值冲突,当前阶段无法确定 font-size 属性,需要留待后续流程解决。

而对于 <p></p> 元素来说,当前阶段就能确定 font-size 属性值为 16px

判断层叠值的优先级(Cascaded Value)

什么是层叠值

层叠值(cascaded value),也译作级联值,指的是对多个来源的各级样式(包括浏览器自带样式、浏览器用户自定义样式、内联样式、外部样式、行间样式)的默认值或声明值进行层层叠加计算后的结果值。

该结果值是参与层叠计算的样式值中符合层叠计算规则的优先级最高的值,要么是某级样式中的一个默认值,要么是某级样式中的一个声明值。因此,层叠值的计算相当于各级样式优先级的计算,最后以优先级最高的为准。

执行流程

在该流程中,浏览器经过 cascading 阶段处理多个声明值冲突的属性优先级的问题。cascade,即瀑布,也就是从上往下依次解析。

对于发生冲突的属性值在按层叠计算规则计算之后,如果该属性最终有“胜出的”声明值,则该值为层叠值。

CSS 优先级判断规则

不同来源的优先级判断

核心:就近原则

内联(行内)样式 > 内部样式 = 外部样式

对于内部与外部样式表,同权重下谁后加载的优先级高,不同权重下权重高的优先级高。

对于内联样式,权重等级为1000,不论内部 or 外部样式权重叠得再高,在非 !important 或同时加上 !important 下,内联样式优先级恒高于内部或外部样式。仅在非内联样式使用 !important 下,优先级大于内联样式。

相同来源的优先级判断

!important(10000) > 内联样式(1000) > id选择器(100) > 类、伪类、属性选择器(10) > 标签选择器(1) > * 任意符选择器(0)

注意:低等级优先级选择器永远无法超过高等级优先级选择器,即例如某个元素拥有 999 个类和 1 个id,叠加 999 个类选择器设置的属性与用 id 选择器设置的属性发生冲突的时候,依然是 id 选择器设置的样式生效。

例如对于以下用户样式表

p {
    font-size: 16px;
}
p.text {
    font-size: 1.2em;
}

对于 <p class="text"></p> 元素来说,p.text 的权重更高,该阶段保留 font-size: 1.2em; 属性。由于 1.2em 为相对单位,故仍需后续流程处理单位转化获得确切值。

确定剩余未声明属性的指定值(Specified Value)

什么是指定值

如果当前文档的各级样式层叠后的层叠值不是空值,则该 层叠值 为指定值。[例 1]

如果层叠值是空值,则以该属性的 默认值 作为指定值,具体而言:

  • 如果该属性为继承属性,并且其所在元素不是文档的根元素,则使用父元素的 计算值 作为指定值。[例 2]
  • 如果该属性为非继承属性,或是文档根元素的属性,则将CSS规范中针对该属性所定义的 初始值(initial value) 作为指定值

例 1:各级样式层叠计算后,color 属性的值为 green,则 greencolor 属性的指定值

例 2:在一个 <div> 内部放置一个段落 <p>,这个 <div>font 属性的计算值为 "Arial",而 <p>font 属性属于继承属性,那么它的 font 属性的指定值就会继承为"Arial"

执行流程

在该流程中,浏览器经过 defaulting 阶段,为没有声明值的属性指定一个确切的值,此处流程执行后保证指定值不为空。但指定值还不一定能直接渲染,例如 font-size: 1.2em

默认值

CSS 中,默认值(default value)与初始值(initial value)不完全相同。

默认值(default value)指的是没有显式地为 CSS 属性声明一个值的情况下,所默认使用的隐式缺省值,并非各浏览器自带的 CSS 默认值。

  • 对于非继承属性(CSS 属性一览表中 inherited 为 no 的属性),默认值为初始值 initial;
  • 对于继承属性(CSS 属性一览表中 inherited 为 yes 的属性):
    • 若不是根元素(根元素是指:root 选择器匹配到的元素,在 html 文档中一般指的是 html 元素,但也可能不是),则默认值为继承值 inherit;
    • 若是根元素,则默认值为初始值 initial。

初始值

在W3C CSS规范的每个CSS属性定义表中,已经给出了属性的初始值(initial value)

如下图 font-size 属性表所示:

image.png

继承

向父元素寻找可继承属性是否存在,若有则继承相应的属性值。

  • 跟文本、声音等与内容相关的属性,比如 font、color、text-align、line-height、white-space、speak、voice-family、volume 等属性,都是可继承属性;

  • 跟框模型、定位等与布局相关的属性,比如 background、border、display、float、height、left、overflow、vertical-align、z-index 等属性,都是非继承属性。

注意:  在实践中,由于各个浏览器会设定某些常用元素的常用CSS属性的值(即前文所述的浏览器自带值),因此只有在浏览器并没有显式地设定其值,并且其他各级样式(包括元素的style属性所设置的行间样式)也没有显式地设定其值的情况下,才会最终使用上述的默认值。

转化为计算值(Computed Value)

什么是计算值

计算一些特殊的值(inherit、initial、unset 和 revert)或相对值(如 em 单位或百分比值)所获得的值,称为 计算值(computed value)

执行流程

在该流程中,浏览器经过 resolving 阶段,将一些相对值(em,% …)或者关键字(inherit, initial …)转化成绝对值,包括相对路径转化为绝对路径等。

在该流程结束后,所有值转化为在不进行实际布局的情况下所能获得最具体的值,同时也意味着对于 width: 100% 这样的属性依旧保留百分比,留待后续流程转化为绝对值。

将计算值进一步转化为使用值(Used Value)

什么是使用值

在取得计算值、并完成剩余计算(如果有的话)后的结果值称为 使用值(used value)

执行流程

在该流程中,浏览器经过 formatting 阶段,将计算值中尚存的相对值进一步全部转换为绝对值。

在该流程结束后,所有计算值已全部被转化为绝对值,不会再有相对值或关键字。

计算值与使用值的辨析

resolving 阶段获得的计算值,只能转化能够直接通过分析 CSS 样式和 HTML 文档就可以计算出绝对值的相对值。例如对父级元素设置 font-size: 20px;,子元素设置 font-size: 2em; width: 60%; margin: 1em。则在该阶段只能计算出子元素的 font-sizemargin 的绝对值为 40px20px。而 width 属性的 60% 只能在 formatting 阶段页面布局获取绝对值的时候才能转化。

获得最终用于渲染的实际值(Actual Value)

什么是实际值

在渲染时实际生效的值,称作 实际值(actual value)

执行流程

在该流程中,浏览器经过 constraining 阶段,将使用值中的小数像素值转化为近似整数用于实际渲染。

注意:该流程会处理属性与属性相关的约束。

例如:对 body 设置属性 width: 100%; max-width: 1200px

  • 若浏览器视窗宽度大于 1200px,则 body 宽度为 1200px
  • 若浏览器视窗宽度小于 1200px,则 body 宽度为 视窗宽度

最终浏览器将最终获得的实际值渲染到页面上,就是我们看到的页面 CSS 效果了


思考和总结

从一天起床就开始准备写这篇笔记,中午吃完饭开始动工,写到吃晚饭的时候。中途暴露出了自己很多基础知识不牢固的问题,很多内容没办法自己能很好地完整表达出来,需要不断地去复习查询资料,耗费了大量时间。

不过收获同样也是非常值得的,纠正了之前一直以为内部样式优先级大于外部,内联和其他优先级的疑虑等问题。更深入理解了浏览器的渲染过程。

同时也感谢非常多的大佬在网上分享的很多资料,和自己知识精华的总结,没有他们我根本写不出来这篇笔记。

引用参考

聊聊css属性值的计算过程 - 简书 (jianshu.com)

刨根究底CSS(2):CSS中的各种值——初始值,就是默认值吗? - 知乎 (zhihu.com)

font-size - CSS: Cascading Style Sheets | MDN (mozilla.org)

```