在 构建对象模型 中,我们根据 HTML 和 CSS 输入构建了 DOM 树和 CSSOM 树。 不过,它们都是独立的对象,分别网罗文档不同方面的信息:一个描述内容,另一个则是描述需要对文档应用的样式规则。
DOM 树和 CSSOM 树将合并后在浏览器屏幕上渲染像素
浏览器将 DOM 和 CSSOM 合并成一个 渲染树,它会网罗网页上所有可见的 DOM 内容,以及每个节点的所有 CSSOM 样式信息。
为了构建渲染树,浏览器大体上完成了下列工作:
display: none
属性的节点。渲染对象是和 DOM 元素相对应,但这种对应关系并非一一对应。
非可视化的 DOM 元素不会被插入渲染树中。
例如, <head>
标签以及里面的内容,以及 display:none
的元素也会被去除,但是 visibility
属性值为 hidden
的元素仍会显示。
部分 DOM 元素可对应多个可视化对象。它们往往是具有复杂结构的元素,无法用单一的矩形来描述。
例如, select
元素有 3 个渲染器:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。如果由于宽度不够,文本无法在一行中显示而分为多行,那么新的行也会作为新的呈现器而添加。
另一个关于多渲染器的例子是格式无效的 HTML。根据 CSS 规范,行内元素只能仅包含块状元素或行内元素中的一种。如果出现了混合内容,则应创建匿名的块状渲染对象,以包裹行内元素。所以我们平时的 inline-block
可以设置宽高。
部分渲染对象对应于 DOM 节点,但树中所在的位置与 DOM 节点不同。
例如,浮动定位和绝对定位的元素处于正常的文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。
构建渲染树之前,需要计算每一个渲染对象的可视化属性。这是通过计算每个元素的样式属性来完成的。
样式包括来自各种来源的样式表、行内样式元素和 HTML 中的可视化属性。其中后者经过转化以匹配 CSS 样式属性。
样式表的来源包括浏览器的默认样式表、由网页作者提供的样式表以及由浏览器用户提供的用户样式表(浏览器允许您定义自己喜欢的样式。以 Firefox 为例,用户可以将自己喜欢的样式表放在 Firefox Profile 文件夹下)。
样式计算存在以下难点:
WebKit 节点会引用样式对象 (RenderStyle)。这些对象在某些情况下可以由不同节点共享。这些节点是同级关系,并且:
:hover
状态,而另一个不是)id
inline
样式属性+
选择器以及 :first-child
和 :last-child
等选择器。样式规则来源:
p {color: blue;}
<p style="color: blue" />
<p bgcolor="blue" />
后两种很容易和元素进行匹配,因为元素拥有样式属性,而且 HTML 属性可以使用元素作为键值进行映射。
样式表解析完毕后,系统会根据选择器将 CSS 规则添加到某个哈希表中。这些哈希表的选择器各不相同,包括 ID、类名称、标记名称等,还有一种通用哈希表,适合不属于上述类别的规则。如果是 ID 选择器,规则就会添加到 ID 表中;如果是类选择器,规则就会添加到类表中,以此类推。
这种处理可以大大简化规则匹配。我们无需查看每一条声明,只要从哈希表中提取元素的相关规则即可。这种优化方法可排除掉 95% 以上规则,因此在匹配过程中根本就不用考虑这些规则。
我们以如下的样式规则为例:
p.error {color: red;}#messageDiv {height: 50px;}div {margin: 5px;}
第一条规则将插入类表,第二条将插入 ID 表,而第三条将插入标记表。
<p class="error">an error occurred</p><div id="messageDiv">this is a message</div>
我们首先会为 p
元素寻找匹配的规则。类表中有一个 error
键,在下面可以找到 p.error
的规则。div
元素在 id
表(键为 id
)和标记表中有相关的规则。剩下的工作就是找出哪些根据键提取的规则是真正匹配的了。
样式对象具有与每个可视化属性一一对应的属性(均为 CSS 属性但更为通用)。如果某个属性未由任何匹配规则所定义,那么部分属性就可由父代元素样式对象继承。其他属性具有默认值。
如果定义不止一个,就会出现问题,需要通过层叠顺序来解决。
某个样式属性的声明可能会出现在多个样式表中,也可能在同一个样式表中出现多次。这意味着应用规则的顺序极为重要。这称为 层叠 顺序。根据 CSS2 规范,层叠的顺序为(优先级从低到高):
important
声明important
声明浏览器声明是重要程度最低的,而用户只有将该声明标记为 important
时才会覆盖网页作者的声明。同等级别的声明将根据 特异性 以及它们被定义时的顺序进行排序。HTML 可视化属性将被转换为匹配的 CSS 声明,它们被视为最低优先级的作者规则。
选择器的特异性由 CSS2 规范定义:
style
属性,而不是带有选择器的规则,则记为 1,否则记为 0(=a)将四个数字按 a-b-c-d
这样连接起来(位于大数进位的数字系统中),构成特异性。
您使用的进取制取决于上述类别中的最高计数。
例如,如果 a = 14
,您可以使用十六进制。如果 a = 17
,那么您需要使用十七进制;当然不太可能出现这种情况,除非是存在如下的选择器:html body div div p ...
(在选择器中出现了 17 个标记,这样的可能性极低)。
🌰 代码示例:
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */