💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 简介 当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。 Firefox将渲染树中的元素称为frames,WebKit则用renderer或渲染对象来描述这些元素。 <br> <br> # 渲染树和Dom树的关系 **渲染对象和Dom元素相对应,但这种对应关系不是一对一的**,不可见的Dom元素不会被插入渲染树,例如head元素。另外,display属性为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出现在渲染树中)。 <br> **还有一些Dom元素对应几个可见对象**,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样,当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添加。另一个多个渲染对象的例子是不规范的html,根据css规范,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。 <br> 一些渲染对象和所对应的Dom节点不在树上相同的位置,例如,浮动和绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。 <br> ![](https://box.kancloud.cn/3e4ce70199dd6cc78e7dbd615bc2d774_731x396.png) <br> <br> # 创建树的流程 Firefox中,表述为一个监听Dom更新的监听器,将frame的创建委派给Frame Constructor,这个构建器计算样式(参看样式计算)并创建一个frame。 <br>   Webkit中,计算样式并生成渲染对象的过程称为attachment,每个Dom节点有一个attach方法,attachment的过程是同步的,调用新节点的attach方法将节点插入到Dom树中。 <br>   处理html和body标签将构建渲染树的根,这个根渲染对象对应被css规范称为containing block的元素——包含了其他所有块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,Firefox称它为viewPortFrame,webkit称为RenderView,这个就是文档所指向的渲染对象,树中其他的部分都将作为一个插入的Dom节点被创建。 <br> <br> # 样式计算 创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到。 <br>   样式包括各种来源的样式表,行内样式元素及html中的可视化属性(例如bgcolor),可视化属性转化为css样式属性。 <br>   样式表来源于浏览器默认样式表,及页面作者和用户提供的样式表——有些样式是浏览器用户提供的(浏览器允许用户定义喜欢的样式,例如,在Firefox中,可以通过在Firefox Profile目录下放置样式表实现)。 <br> 计算样式的一些困难: 1. 样式数据是非常大的结构,保存大量的样式属性会带来内存问题。 2. 如果不进行优化,找到每个元素匹配的规则会导致性能问题,为每个元素查找匹配的规则都需要遍历整个规则表,这个过程有很大的工作量。选择符可能有复杂的结构,匹配过程如果沿着一条开始看似正确,后来却被证明是无用的路径,则必须去尝试另一条路径。 例如,下面这个复杂选择符 ~~~   div div div div{…} ~~~ 这意味着规则应用到三个div的后代div元素,选择树上一条特定的路径去检查,这可能需要遍历节点树,最后却发现它只是两个div的后代,并不使用该规则,然后则需要沿着另一条路径去尝试 3. 应用规则涉及非常复杂的级联,它们定义了规则的层次 <br> <br> ## 对规则进行处理以简化匹配过程 样式规则有几个来源: * 外部样式表或style标签内的css规则 * 行内样式属性 * html可视化属性(映射为相应的样式规则)   后面两个很容易匹配到元素,因为它们所拥有的样式属性和html属性可以将元素作为key进行映射。 <br> 为了解决这个问题,可以先对规则进行处理,以使其更容易被访问。 <br>   解析完样式表之后,规则会根据选择符添加一些hash映射,映射可以是根据id、class、标签名或是任何不属于这些分类的综合映射。如果选择符为id,规则将被添加到id映射,如果是class,则被添加到class映射,等等。 <br>   这个处理是匹配规则更容易,不需要查看每个声明,我们能从映射中找到一个元素的相关规则,这个优化使在进行规则匹配时减少了95+%的工作量。 <br>   来看下面的样式规则: ~~~ p.error {color:red} #messageDiv {height:50px} div {margin:5px} ~~~ <br> 第一条规则将被插入class映射,第二条插入id映射,第三条是标签映射。 <br> 下面这个html片段: ~~~ <p class="error">an error occurred </p> <div id=" messageDiv">this is a message</div> ~~~ 我们首先找到p元素对应的规则,class映射将包含一个“error”的key,找到p.error的规则,div在id映射和标签映射中都有相关的规则,剩下的工作就是找出这些由key对应的规则中哪些确实是正确匹配的。 例如,如果div的规则是 ~~~ table div {margin:5px} ~~~ 这也是标签映射产生的,因为key是最右边的选择符,但它并不匹配这里的div元素,因为这里的div没有table祖先。   Webkit和Firefox都会做这个处理。 <br> <br> # 以正确的级联顺序应用规则   样式对象拥有对应所有可见属性的属性,如果特性没有被任何匹配的规则所定义,那么一些特性可以从parent的样式对象中继承,另外一些使用默认值。 <br>   这个问题的产生是因为存在不止一处的定义,这里用级联顺序解决这个问题。 <br> ## 样式表的级联顺序   一个样式属性的声明可能在几个样式表中出现,或是在一个样式表中出现多次,因此,应用规则的顺序至关重要,这个顺序就是级联顺序。根据css2的规范,级联顺序为(从低到高):   1. 浏览器声明   2. 用户声明   3. 作者的一般声明   4. 作者的important声明   5. 用户important声明 <br>   浏览器声明是最不重要的,用户只有在声明被标记为important时才会覆盖作者的声明。具有同等级别的声明将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css声明,它们被视为最低优先级的作者规则。 <br> ## Specifity Css2规范中定义的选择符specifity如下: * 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a) * 计算选择器中id属性的数量(=b) * 计算选择器中class及伪类的数量(=c) * 计算选择器中元素名及伪元素的数量(=d) <br>   连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity。这里使用的基数由分类中最高的基数定义。例如,如果a为14,可以使用16进制。不同情况下,a为17时,则需要使用阿拉伯数字17作为基数,这种情况可能在这个选择符时发生html body div div …(选择符中有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 */ ~~~ <br> <br> #参考资料 [浏览器工作原理(六):渲染树构建](https://blog.csdn.net/lxcao/article/details/52862176)