渲染流程就像一个流水线,每一步的输出都是下一步的输入。
渲染流程如何开始
从用户输入一个url开始,浏览器通过网络线程
向服务端请求页面,服务端向浏览器返回一个html页面。
被解析后的html页面通过网络线程
送入 消息队列
,等待 渲染主线程
的执行
渲染流程
解析HTML
以下过程在 网络线程
中完成:
由于服务器以 字节流
的方式传输html页面,浏览器获得服务器的响应时,并不认识html页面中的内容。于是,浏览器需要将字节流转化为字符串。但是,浏览器仍然不认识字符串里的内容,因此,还需要进行进行 标记化
,即解析出html标签、属性以及文本内容,并进行结构化、语义化,形成一个 标记流
。这为构建DOM Tree提供了基础。
总结一下,为了构建DOM Tree,浏览器做了以下准备:
- 将字节流转解码为字符串
- 将字符串进行标记化,形成一个标记流,使得浏览器识别html页面的结构
构建Dom Tree
渲染主线程通过事件循环,将html页面从消息队列中取出。开始进行真正的渲染。
解析一个个DOM元素,构建出DOM Tree,形如:
外部资源
当遇到了外部资源怎么办?例如
渲染主线程开启时,浏览器还会开启另一个线程,称为 预解析线程
,该线程用于扫描html页面,寻找外部资源。当遇到外部资源时,开启 网络线程
,向服务器请求相应的外部资源。
解析js
- 如果外部资源为js文件
渲染主线程被阻塞,转而去执行预解析线程通过网络线程下载的js文件。为什么要阻塞渲染主线程呢?因为js可能会直接操作DOM元素,这样,就影响了DOM Tree的结构。因此,需要暂停主线程中对DOM Tree的创建,直到执行完js。然而,这样就使得浏览器渲染速度变慢,所以建议将
构建CSSOM
- 如果外部资源是css文件
由 预解析线程
完成CSS解析。CSS 的解析在经历了从字节数据、字符串、标记化后,最终也会形成一颗 CSSOM (CSS Object Model/CSS对象模型),形如:
注意:CSSOM构建并不会阻塞DOM Tree的构建过程。此时,渲染主线程和预解析线程并行执行。
构建Render Tree(渲染树)
当DOM Tree和CSSOM被构建完成后,渲染主线程将两颗树合成为一颗树,称为Render Tree。渲染树包含了所有占用布局空间的结点和样式信息。形如:
注意:
在构建Render Tree时,CSS文件可能还未被完全解析。当预解析线程构建CSSOM时,要阻塞Render Tree的构建,即阻塞渲染主线程。因为Render Tree需要CSSOM。
问题:Render Tree和CSSOM有什么区别
有些结点不可见,比如设置了display: none,这样的结点不会出现在Render Tree中,会出现在CSSOM中。
但是,要注意 visibility:hidden 的节点仍然占用布局空间,只是不可见。所以仍然在Render Tree中。
布局:构建Layout Tree(布局树)
虽然已经构建出了Render Tree,但是,Render Tree忽视了元素的几何信息,包括宽度、高度、位置信息。于是,渲染主线程遍历Render Tree通过样式计算,构建出Layout Tree(布局树)。
分层:构建Layer Tree(层次树)
主线程遍历整颗布局树,构建出Layer Tree。下面是网页的分层结构:
这样做的好处是:当单个dom元素被修改时,浏览器只需重新处理该元素所在的层,而不是整个页面,这样大大提高了渲染速度。
生成绘制指令
主线程为每一个层生成一系列的绘制指令。这些绘制指令描述了如何在屏幕上绘制各种图形和文字。接下来,这些绘制指令交给 合成线程
。渲染主线程的工作就告一段落了。
分块
- 由
合成线程
完成分块的操作,该线程会从线程池调用多个线程帮助来帮他完成分块工作。 - 分块是将每个图层分成一个个小块。
光栅化
- 由
GPU线程
完成:当分块完成后,合成线程将所有的块信息交给GPU线程
什么是光栅化
将每个块转化成位图图像,位图由一个个像素点组成,每个像素点都有自己的颜色,用于描述图像内容。
绘制
- 光栅化完成后,
合成线程
拿到每个块的位图信息,接着生成一个个指引
。这些指引标识出每个位图应该绘制在屏幕的哪个位置。 - 合成线程向浏览器提交一个个渲染帧,以形成合成帧。
- 合成帧交给
GPU线程
,由该线程完成最后的成像。
注意
- trasform发生在绘制阶段,这就是 transform 属性能够高效执行的根本原因。
- 当页面滚动的事件时,合成线程会将合成帧发送给 GPU,这样可以快速更新页面,而不需要经过完整的渲染流程。
回流和重制
回流(reflow)
是什么
重新构建布局树,是一个很昂贵的操作。
引发回流的事件
- 删除/添加DOM元素
- 改变DOM元素的尺寸/位置
- 浏览器窗口发生变化
优化
- 当js对DOM元素的布局进行操作时,js会合并这些操作,进行统一计算。这样,就减少了回流次数。不过,由于这个异步操作,通过js获得dom元素的布局属性时,可能无法得到最新值。
重制(repaint)
是什么
改变了可见样式之后,需要重新生成绘制指令。
注意:虽然布局树包含了所有可见元素的样式信息,但不意味着重制会改变布局树。重绘只会根据分层,重新计算绘制指令。
优化
- 利用第三方库统一样式,减少样式修改
- 针对性重制