浏览器渲染流程

当浏览器的网络引擎接收到了网络请求返回的HTML文档时,它会将其解析成一系列的渲染任务,并将这些任务放入渲染主线程的消息队列中。在事件循环机制的作用下,渲染主线程会按照队列的顺序依次处理这些渲染任务。
当渲染主线程开始渲染操作时,它会从消息队列中取出相应的渲染任务,并根据这些任务的要求对页面进行相应的渲染。在渲染过程中,渲染主线程会根据HTML文档中的元素和样式计算出相应的绘制指令,并将这些指令发送给GPU进行绘制。
通过这样的流程,最终将精美的网页呈现在用户眼前。
渲染过程阶段

渲染过程包含多个阶段,这些阶段相互衔接并形成一条流水线。具体来说,这些阶段包括:解析HTML、样式计算、布局、分层、绘制、分块、光栅化和作画。
在每个阶段,都会有一个输入和一个输出。输入是上一个阶段的输出,而输出将成为下一个阶段的输入。这种流水线式的结构确保了渲染过程的连续性和一致性。
解析HTML

将HTML解析成DOM树(Document Object Model)和cssom树(CSS Object Model)

将HTML字符串转换为document对象,方便数据的使用和js的访问

将样式表转换为styleSheetList对象,方便数据使用和js的访问
在样式表列表styleSheetList中,包含了多个不同种类的样式表。这些样式表各自拥有众多不同的样式规则,每个规则又包含一个或多个选择器,以及相应的样式style
- 样式表类型分为: 行内样式、内部样式、外部样式、浏览器默认样式 、
- js获取行内样式:dom.style
- js获取所有类型样式表:document.styleSheet,通过addRule方法进行添加规则
HTML 解析过程中遇到 CSS 代码

为了提高效率,浏览器会启动一个预解析线程率先下载和解析css
当渲染主线程遇到一个链接(link)标签指向外部CSS文件时,它会立即开始下载该文件,而不会等待CSS文件完全下载和解析完成。这是因为下载和解析CSS文件的工作是在一个独立的预解析线程中进行的。因此,主线程在CSS文件尚未完全下载和解析的情况下不会等待,而是继续解析后续的HTML代码。这也就解释了为什么CSS不会阻塞HTML解析的根本原因。
HTML 解析过程中遇到 JS 代码

当渲染主线程遇到JS会停止一切行为,等待预解析线程下载完后才能执行
在主线程进行HTML解析的过程中,如果遇到脚本(script)标签,它会停止HTML解析,转而等待相关的JavaScript文件下载完成。一旦全局代码被解析并执行后,才会继续解析HTML。这是因为JavaScript的执行过程可能会修改当前的DOM树,因此,DOM树的生成必须暂停。这就是JavaScript能够阻塞HTML解析的根本原因。
样式计算

将HTML和CSSOM树解析后,主线程将遍历DOM树,为每个节点精确计算出最终的样式,这一过程被称为计算样式(Computed Style)。最终,主线程会将计算后的样式应用于DOM树,从而得到最终的渲染树。
布局

在CSS中,将元素的display属性设置为none会导致该元素在DOM树中隐藏。相应地,该元素也不会出现在布局树中,因为它不会影响页面的布局和渲染。

在CSS中,使用::before伪元素会在DOM树中创建一个额外的节点,该节点会在布局树中生成一个额外的分支。这意味着它会在页面布局和渲染时占据一定的空间。

- 内容必须在行盒中
- 行盒和快盒不能相邻
注意:
- 行内元素和块级元素是指html的标签
- 行盒和块盒是指布局的样式
在布局阶段,主线程会逐一浏览DOM树的每个节点,并计算其几何属性,如宽度、高度以及相对于其包含块的位置。在大部分情况下,DOM树和布局树之间的关系并非一一对应,这意味着每个DOM节点可能对应于布局树中的多个节点,反之亦然。
分层

分层的优势在于,当某一层在未来发生改变时,只需对该层进行后续处理,从而有效提升工作效率。
滚动条、堆叠上下文、变换(transform)和透明度(opacity)等样式都会在一定程度上对分层的结果产生影响。然而,我们可以通过使用will-change属性来更大程度地影响分层结果,以进一步优化性能。
css的will-change属性和作用
CSS的will-change属性是一个用于通知浏览器某个元素即将发生变化的属性。它可以被应用到任何元素上,用于提前告知浏览器该元素将要有哪些属性进行改变,从而优化渲染性能。
通过在元素上设置will-change属性,开发者可以明确指示浏览器对该元素进行优化处理。这样一来,浏览器可以提前分配资源和准备工作,以便在实际改变发生之前进行相应的合成操作。这样做有助于避免不必要的重绘和重排,提高页面的响应速度和动画的流畅度。
will-change属性可以接受多个属性值,表示将要改变的属性。例如,.element{will-change:transform,opacity;}在上述例子中,我们告诉浏览器该元素将要发生的变化是transform和opacity属性。浏览器会根据这个信息进行相应的预处理,并为元素分配更高效的渲染方式,如创建一个独立的图层或使用GPU加速等。
尽管will-change属性可以带来性能优化,但滥用它可能会导致负面影响。因为过多地使用will-change可能会导致浏览器过度优化,导致资源的浪费。因此,应仅在确实需要进行频繁改变的元素上使用will-change,以避免不必要的复杂性和性能问题。
总结起来,will-change是一个用于告知浏览器某个元素即将发生变化的CSS属性,它可以帮助优化渲染性能,提高页面的响应速度和动画的流畅度。
绘制

对分层后的布局进行绘制,并为每一层生成如何绘制的指令,最终形成一个包含所有绘制指令的集合。这样可以在需要时根据该指令集对页面进行绘制并呈现最终结果。

当我们浏览器的渲染过程进行到绘制阶段后,后续步骤将由其他线程负责执行。这样,主线程可以专注于其他任务,提高浏览器的整体性能。
分块

将每一层进一步分割为多个小的区域,这样可以更有效地利用浏览器的渲染资源,提高绘制效率。

主线程将每个图层的绘制指令和信息提交给合成线程,剩余的工作将由合成线程全权负责。
合成线程首先会对每个图层进行分块处理,将其细分为更多的小区域。
为了提高效率,它会从线程池中获取多个线程来共同完成这个分块过程。
光栅化

每个小块都会被转化为位图,而在这些位图中,当前视口内的位图会率先进行光栅化处理。随后,其他的块也会陆续进行光栅化处理。

合成线程会将各个块的信息发送给 GPU 进程,以利用其高效的处理能力来迅速完成光栅化操作。GPU 进程将启动多个线程来并行处理这些块,并且会优先处理靠近视口的区域。最终,光栅化的结果将以一块一块的位图形式呈现出来。
作画

合成线程在获取每个层、每个块的位图后,会生成一系列的"指引"(quad)信息。这些指引会详细标识出每个位图应该被绘制到屏幕的哪个位置,同时也会考虑到如旋转、缩放等可能的变形。
由于这些变形操作都在合成线程中完成,与主渲染线程完全独立,因此transform属性的使用效率能够得到大幅提升。
最后,合成线程将这些quad提交给GPU进程。GPU进程则会通过系统调用来将这些指令提交给底层的GPU硬件,最终完成最终的屏幕成像。
最终流程

思考
- 什么是reflow

reflow 的本质就是重新计算页面元素的位置和大小,即重新计算 layout 树。当对页面元素进行修改操作时,这些操作会影响到页面的布局,因此需要重新计算 layout 树以反映这些更改。
为了避免连续的多次操作导致 layout 树的反复计算,浏览器会将这些操作合并在一起,然后在 JS 代码全部执行完毕后再进行一次性的计算和渲染。因此,当 JS 修改了页面元素的属性时,这些改动造成的 reflow 是异步完成的。
同样地,由于 reflow 是异步的,当 JS 代码在获取页面元素的布局属性时,可能会获取到旧的布局信息而不是最新的布局信息。
最终,浏览器在权衡性能和准确性后,决定在获取属性时立即进行 reflow。
- 什么是repaint

repaint 的本质是指浏览器根据修改后的分层信息重新计算并执行绘制指令。当页面的可见样式发生改变时,这些改变需要重新计算并反映在页面的视觉表现上,这就会引发 repaint。
同时,元素的布局信息也属于可见样式的一部分,因此当布局发生变化时,一定会引发 reflow,从而触发 repaint。
- 为什么 transform 效率⾼

因为transform属性只会影响渲染流程的最后一个"绘制"阶段,而不会对布局或绘制指令产生任何影响。
由于绘制阶段是在合成线程中进行的,因此transform属性的变化几乎不会对渲染主线程产生影响。相反,无论渲染主线程的工作负荷多么繁重,都不会影响到transform属性的变化。