当我们访问网站时,通过网络请求得到HTML文档,然后开启一个渲染任务。
既:浏览器开启一个渲染进程,在渲染进程中有一个渲染主线程。在事件循环的作用下,开启渲染流程。
渲染流程:
A. 解析HTML文档。
通过网络请求得到的HTML文档实际是一个字符串文档
渲染,可以想象通过render(html)解析html。
1.在解析的过程中,会先预解析外部引用的css js资源,进行下载。
2.然后开始从头解析HTML文档,遇到CSS就解析CSS,遇到JS就解析JS。
- 遇到<link>引用外部css资源,若预加载没有下载完成,渲染线程会跳过,继续解析后面
的HTML内容。这是CSS不会阻塞HTML解析的原因。
- 遇到<script>引用外部JS资源,则会等待其下载完成后,解析并执行JS代码后,才继续解析
后面的HTML内容。这是JS会阻塞HTML解析的原因。
(因为JS代码可能会对之前已经解析的HTML结构或CSS样式进行修改)
B. 样式计算
解析完成,得到DOM树和CSSOM树。通过样式计算,得到计算样式后的DOM树。(把DOM树和CSSOM树结合起来,使DOM树的节点拥有CSS样式,得到带有样式的DOM树)
C. 布局
遍历以上得到的DOM树,得到layout树。(计算宽高,相对包含块的位置)
这里生成的layout树并不会和DOM树一一对应,例如display:none节点不会被加载到layout树中。
D. 分层
举个例子:在三维空间中的z轴分层,例如z-index,但是实际并不一样。
浏览器并不会每次都渲染整个页面,而是将页面分成多个层次,每次只渲染需要改变的层。每个分层都会占用一定的内存资源,所以浏览器并不能分太多的层。
(可以在CSS中使用will-change:transform属性生成一个新图层)
E. 绘制
这里会经过两个线程的处理。
主线程 为每个图层单独产生绘制指令集合,描述这一层该如何画。
蓝色为窗口,黑色为分块 。
完成绘制后将每个图层的绘制信息交给合成线程进行分块处理,合成线程将其分成更多的小区域,通过开启多个线程来分块工作。
F. 光栅化
合成线程将块信息交给GPU,快速处理,得到一块一块的位图。
G. 画
合成线程拿到每个图层的块位图后,生成指引信息,指引是标识每个位图会画到那个具体位置。合成线程会把指引提交给GPU,GPU使用显卡,渲染页面。
关于reflow:
reflow 的本质就是重新计算 layout 树。当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。
当修改css或html时,就会触发修改CSSOM树和DOM树,然后修改布局树,也就是B,C,D,E,F,G步骤的重新执行。重新把页面画一遍。
为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reflow 是异步完成的。
因此在开发中,有时用JS并不能获取到最新的布局属性。
关于repaint:
repaint 的本质就是重新根据分层信息(也就是E步骤)计算了绘制指令。
通过绘制指令进行F,G
当页面被改动就会引起reflow,而reflow一定会引起repaint。