一、 渲染的流程图
二、渲染流程有四个主要步骤
- 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树
- 构建Render树 - 接下来CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),
- 布局Render树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
- 绘制Render树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来
三、渲染的细节
1. 构建DOM树
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于
2.预加载扫描器
浏览器构建DOM树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如CSS、JavaScript和web字体。
多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源,以便在主HTML解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。预加载扫描仪提供的优化减少了阻塞。
3.构建CSSOM树
第二步是处理CSS并构建CSSOM树。CSS对象模型和DOM是相似的。DOM和CSSOM是两棵树. 它们是独立的数据结构。浏览器将CSS规则转换为可以理解和使用的样式映射。
4.生成Render树
第三步是将DOM和CSSOM组合成一个Render树,渲染树从DOM树的根开始构建,遍历每个可见节点。
Render树是用于显示,那不可见的元素当然不会在这棵树中出现了,譬如 。除此之外,display等于none的也不会被显示在这棵树里头,但是visibility等于hidden的元素是会显示在这棵树里头的。
5.布局与绘制
布局是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。
构建渲染树后,开始布局。渲染树标识显示哪些节点(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。
renderer的内容显示在屏幕上。绘制工作是使用UI后端组件完成的。绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。
第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。在我们的示例中,假设初始布局发生在返回图像之前。由于我们没有声明图像的大小,因此一旦知道图像大小,就会有回流。
6.回流与重绘
6.1概念
回流(reflow):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染。(比如元素的显示与隐藏)
重绘(repaint):改变某个元素的背景色、文字颜色、边框颜色等等不影响它布局时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
6.1关系
回流一定伴随着重绘,而重绘却可以单独出现
6.3 reflow与repaint的时机
- display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发生位置变化。
- 有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
- 有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
四、渲染的阻塞
1.渲染的原理
构建dom树当遇到一个CSS文件时,解析也可以继续进行,但是对于
等待获取CSS不会阻塞HTML的解析或者下载,但是它的确阻塞JavaScript,因为JavaScript经常用于查询元素的CSS属性。
2.阻塞的顺序
css加载不会阻塞DOM树的解析
css加载会阻塞DOM树的渲染
css加载会阻塞后面js语句的执行(JavaScript经常用于查询元素的CSS属性。)CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
js会阻塞DOM 构建,直至脚本完成执行(js引擎线程和GUI线程是互斥的)
没有js的理想情况下,html与css会并行解析,分别生成DOM与CSSOM,然后合并成Render Tree,进入Rendering Pipeline;但如果有js,css加载会阻塞后面js语句的执行,而(同步)js脚本执行会阻塞其后的DOM解析(所以通常会把css放在头部,js放在body尾)
五、优化渲染性能
1. 长耗时的JS代码放到Web Workers中执行
web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。
JS代码运行在浏览器的主线程上,与此同时,浏览器的主线程还负责样式计算、布局、绘制的工作,如果JavaScript代码运行时间过长,就会阻塞其他渲染工作,很可能会导致丢帧。
2.尽可能避免触发布局
所以通常会把css放在头部,js放在body尾。
3.对事件进行防抖和节流处理
4.CDN
六、浏览器原理
1. js单线程原因
如果JS是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突;
如果JS是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果,当然我们可以通过锁来解决上面的问题。
但为了避免因为引入了锁而带来更大的复杂性,JS在最初就选择了单线程执行。
2. GUI渲染线程与JS引擎线程
GUI渲染线程与JS引擎线程互斥的,是由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致。
当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。由于GUI渲染线程与JS执行线程是互斥的关系,当浏览器在执行JS程序的时候,GUI渲染线程会被保存在一个队列中,直到JS程序执行完成,才会接着执行。
因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
|