likes
comments
collection
share

前端基础知识——浏览器渲染一个页面的全过程总结

作者站长头像
站长
· 阅读数 9

前言:

如果你在研究“浏览器如何渲染页面”这个老生常谈的话题,并且看了一部分相关文章,那么不妨花5分钟看一下我的理解,或许对你有所帮助。

(最近事情有点多,乱糟糟的,学习也浮躁,文章是我好一段时间前写的了,本还想基于理论再整点优化的实践,也没落实,不过可以放心下面文字的质量我是有信心的)

关键渲染路径(一个概念)

定义:就是我们的浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。

个人理解:这个过程中包含文档对象模型(DOM)的构建,CSS对象模型(CSSOM)的构建,渲染树的构建以及布局、绘制等具体的步骤。感觉关键渲染路径就是在描述页面首屏渲染的流程,所以优化关键渲染路径可以提高渲染性能(首屏渲染优化)。

浏览器渲染流程概图

前端基础知识——浏览器渲染一个页面的全过程总结

一、HTML解析细节(& 思考)

浏览器渲染首先就是解析HTML文件,以构建DOM(document object module)树以及CSSOM树

这里需要一点关于浏览器线程与进程的基础知识:浏览器是多进程模型,其中每个tab页都有一个渲染进程,渲染进程里的常驻线程有:GUI渲染线程,负责解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等都是这个线程去做的;还有就是JS引擎线程,也就是常说的主线程,负责js代码的执行,GUI渲染线程与JS引擎线程是互斥的

前端基础知识——浏览器渲染一个页面的全过程总结

二、构建render tree

通过上面的HTML解析,我们拿到了DOM与CSSOM,并将其合称为render tree,说白了就是给DOM树的每个节点添加上样式属性,当然这个过程中display: none的元素会被删除,不会出现在render tree中。合成过程中还会将一些css属性进行转换,比如:red会变成rgb(255,0,0);相对单位会变成绝对单位,比如em会变成`px。

三、布局

就是根据render tree各节点的一些位置属性生成布局树,说白了就是明确这些元素将来渲染的一个位置。

四、分层

也就是我们的元素布局可能并不是在一个平面上,而是分了若干个图层进行渲染。

分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。

如下简单举几个产生新图层的例子:

  1. 普通元素设置position不为static并且设置了z-index属性,会产生层叠上下文。
  2. 元素的 opacity 值不是 1
  3. 元素的 transform 值不是 none

五、绘制

GUI渲染线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。

这里注意GUI线程到这里就要让位了:完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。

绘制之后还有分块(合成线程首先对每个图层进行分块,将其划分为更多的小区域)、光栅化(光栅化的结果,就是一块一块的位图)操作,但是感觉不是很重要,然后就是最后一步了:画(真正在浏览器上进行渲染展示)

六、画

合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。

指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。

变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。

合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件(硬件加速),完成最终的屏幕成像。

什么是reflow(回流/重排)?

reflow 的本质就是重新计算 layout 树(根据render树生成的布局树)。

当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。

为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reflow 是异步完成的

也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。

浏览器在反复权衡下,最终决定获取(元素位置)属性立即 reflow。

什么是 repaint(重绘)?

repaint 的本质就是上面的第五步,即重新根据分层信息计算绘制指令。

当改动了可见样式后,就需要重新计算,会引发 repaint。

render树之后就是计算layout布局树,步骤在paint绘制之前,所以 reflow 一定会引起 repaint。

为什么 transform 的效率高?

因为 transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个「draw」阶段 (处于绘制的最后一个阶段,不会牵动前面的步骤执行)

由于 draw 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染线程无论如何忙碌,也不会影响 transform 的变化。 (transform所属的线程为合成线程,不抢占渲染线程以及js引擎线程)