当浏览器的网络线程收到HTML文档之后,会产生一个渲染任务并且会将其传递给渲染主线程的消息队列,在事件循环机制的作用下,渲染的主线程会将消息队列中的渲染任务取出来进行渲染流程。其中前五步骤为渲染的主线程。
如果想更了解浏览器的事件循环机制,请转接这篇文章;
第一步解析Html
浏览器为了提高解析效率,会在开始解析之前进行一个预解析的线程,该线程主要任务就是预解析下载外部的CSS样式和外部JS样式,在解析完成后,会生成DOM树和CSSOM树,CSSOM树包括行内样式,默认样式,内部样式,外部样式。
为什么CSS不会阻塞HTML解析,而JS会阻塞HTML解析进程???
如果主线程解析到link
的位置,而此时外部的CSS样式并没有解析下载完成,主线程不会等待会直接解析后面的HTML,这是因为CSS外部样式的解析下载是在预解析线程中进行的;
而如果解析进行到script
的位置,此时主线程会停下等待JS文件解析下载好,之后再进行后续的HTML解析,这是因为JS的代码执行过程中可能会更改DOM树的结构,所以DOM树的生成必须暂停;
如果遇到的script
标签带有defer
或async
属性;
defer
下载不阻塞页面渲染,等到HTML文档解析完成之后,DOMContentLoaded事件的触发前, 才执行该脚本async
下载不阻塞页面渲染,但是会停止HTML文档的解析,先执行脚本之后再进行HTML解析
第二步样式计算
主线程会遍历得到的DOM树,依次为树中的每个节点计算出他们的最终样式,这个被称为Computed Style
,在这个过程中,预设值会变成绝对值,相对单位会变成绝对单位;例如:red
会变成rgb(255,0,0)
,em
会变成px
,随后就会得到一颗带有样式的DOM树。
第三步布局
布局完成后会得到布局树(layout树)。在布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。
大部分时候,DOM 树和布局树并非一一对应。
DOM树:与 HTML 标签一一对应,包括 head 和隐藏元素
布局树:不包括 head 和隐藏元素,布局树只会展示 DOM 树下的几何信息
比如display:none
的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。
第四步分层
分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。
主线程需要遍历布局树来创建一棵层次树(Layer树), 滚动条,堆叠上下文, transform
,opacity
等样式或多或者的影响分层结果,我们可以通过 will-change
属性更大程度的影响分层结果。如果在页面中我们有一个元素,要经常重排重绘,我们可以通过 will-change: transform;
告诉浏览器,将来元素会发生变化,让浏览器自行决策是否进行单独分层。
第五步绘制
主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。
在完成绘制后,主线程的任务宣告结束。,之后主线程会将每个图层的绘制信息交给合成线程来完成后续工作。
什么是合成线程呢?
合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。
第六步分块
启动合成线程,每个图层进行分块,将其划分为更多的小区域
它会从线程池中拿取多个线程来完成分块工作。
第七步光栅化
一旦Layer 树被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次数的每一层进行光栅化。有的层的可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块,之后将这些小图块分别进行发送给一系列光栅线程进行光栅化,结束后光栅线程会将每个图块的光栅结果存在GPU Process
的内存中。为了优化显示体验,那些在视口中的或者视口附近的层先被光栅化。
文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化。
第八步画
合成线程拿到每个层、每个块的位图后,生成一个个指引信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。
变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。
合成线程会把 指引信息提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。
常见面试题
什么是回流reflow?
当DOM变化影响了元素,比如元素的尺寸、布局、显示隐藏等改变了,需要重写构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这个时候一定会发生回流。为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的reflow
是异步完成的。当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息,所以浏览器会在获取属性时立即 reflow
。
什么是重绘repaint?
当一个元素的外观发生变化(可见样式更改),但是没有改变布局,重新渲染元素的外观。比如background-color
、color
。由于元素的布局信息也属于可见样式,所以reflow
一定会引起 repaint
如何避免回流重绘:
- 避免使用
table
布局 - 尽可能在DOM树的最末端改变
class
- 不要频繁的操作元素的样式
- 避免设置多层内联样式
- 开启GPU加速
- 使用
absolute
或者fixed
,脱离标准文档流