浏览器渲染页面过程

浏览器有许多不同的线程

  1. GUI 渲染线程

  2. JS 线程

  3. 定时器触发线程 (setTimeout)

  4. 浏览器事件线程 (onclick)

  5. http 异步线程

这些线程在浏览器中是互斥的,也就是说同一个时间只会有一个线程在工作。 比如说浏览器在执行GUI 渲染线程呢,那么其他的4个进程,都处于挂起的状态,在队列里面呆着。DOM的渲染对应的就是GUI渲染进程;JS的执行对应的就是JS线程;所以,DOM的渲染与JS代码的运行,在同一瞬间只能有一个在执行。

浏览器渲染页面的过程可以分为两步

1.dom树解析:就是把你所写的各种html标签,生成一个DOM TREE,可以认为就是生成了一个最原始的页面,一点样式都没有,毫无CSS修饰的那种;

2.dom树渲染:浏览器会把本身默认的样式+用户自己写得样式整合到一起,形成一个CSS TREE,而DOM渲染就是指DOM TREE 和 CSS TREE 结合到一起,生成一个Render TREE,呈现出一个带有样式的页面。

浏览器解析+渲染的具体过程如下

1.浏览器将获取的HTML文档由HTML解析器解析成DOM树(DOM树的生成过程中可能会被JS的加载执行而阻塞。)

2.同时由CSS解析器将CSS样式解析成CSS Rule Tree(CSS规则树)

3.将生成的DOM树和CSS规则树合并生成Rendering Tree(渲染树)

4.布局(layout):布局RenderObject树,负责RenderObject树中的元素的尺寸,位置等计算

5.绘制(painting):绘制RenderObject树,绘制页面的像素信息,绘制到页面上。用户可以看到了

其中,前三步执行的比较快,4.5步执行的比较耗时。这是因为可能会发生回流(也叫重排,reflow。重新执行步骤4)及重绘(repaint,重新执行步骤5)。

重排和重绘到底是啥意思呢?接下来,对以上五个步骤进行详解的同时再解释重排和重绘:

第1步可以就是DOM树的解析和构建:

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是,DOM树的生成过程中可能会被JS的加载执行而阻塞(本文末尾解释)。

第2、3步的具体过程:

(1)构建CSSOM树:浏览器解析CSS文件并生成CSS规则树,每个CSS文件都被分析成一个StyleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。

(2)构建渲染树(RenderObject树):通过DOM树和CSS规则树我们便可以构建渲染树。浏览器会先从DOM树的根节点开始遍历每个可见节点。对每个可见节点,找到其适配的CSS样式规则并应用。

渲染树构建完成后,每个节点都是可见节点并且都含有其内容和对应规则的样式。这也是渲染树与DOM树的最大区别所在。渲染树是用于显示,那些不可见的元素当然就不会在这棵树中出现了,譬如。除此之外,display:none的也不会被显示在这棵树里头,但是visibility:hidden的元素是会显示在这棵树里头的。(display:none会导致重排和重绘visibility:hiddden会导致重绘。这是后者的优点,但缺点是此节点一直保存在内存中,占用资源。)

第5步:布局(layout)

布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

第6步:绘制(paint)

在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

replaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。
reflow:意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是Reflow,或是Layout。

有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。 

如何减少回流和重绘:

1:为需要多次回流的元素设置绝对定位或固定定位来脱离文档流,形成新的图层来限制回流和重绘的范围(如轮播图)

2:避免一条一条的修改样式,最好将样式定义为class并一次性更改

3:避免循环操作DOM。创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document

4:对一个元素进行复杂操作时,可以先隐藏,处理完成后再显示,这里所谓的隐藏就是让元素不存在于render tree中(先display:none 隐藏元素,然后对该元素进行所有的操作,最后再显示该元素。因对display:none的元素进行操作不会引起回流、重绘。所以只要操作只会有2次回流)

5:用transform做形变和位移

6:不要使用table布局,因为table的每一个小改动都会导致整个table重新布局
 

阻塞:

1:构建DOM树时,如遇到JS元素时,会阻塞DOM树和CSS规则树的构建,优先执行JS文件

2:构建DOM树时,如遇到CSS元素时,会开启异步请求线程,该线程会先下载CSS文件,再构建CSS规则树,该线程会阻塞JavaScript引擎线程,但不会阻塞DOM树的构建

3:CSS解析和JS解析互斥,也就是说JS解析时会阻塞CSS解析,CSS解析时也会阻塞JS解析

4:JS解析时,如果JS还操作了CSS,而这个CSS还没有下载或构建解析,则会延迟执行JS,直到完成CSS下载构建解析,再会继续执行JS

说明:

1.浏览器在渲染网页时会开启两条线程,渲染引擎线程和JS引擎线程,但这两条线程是互斥的,同时只能有一个线程在执行。也就是说在构建DOM树时,渲染引擎在执行:

2.当遇到JS时:渲染引擎会停止执行,控制权交给JS引擎,当执行JS代码时如果遇到获取DOM,那么如果该DOM还没有解析,则会获取为null,如果JS代码还操作了CSS,而这个CSS如果还没有下载和构建,那么浏览器首先会阻塞JS引擎执行,然后会开启一个异步请求线程,在该线程上,去下载构建CSS规则树,CSS规则树构建完成后,再继续执行JS代码,当JS执行完以后,控制权再次交给渲染引擎去执行。

3.当遇到CSS元素时:也会开启异步线程,去下载构建CSS规则树,但同时也会继续构建后面的DOM树,也就是说DOM解析和CSS解析可以同时进行,但如果后面遇到JS元素,则会阻塞JS引擎线程执行,后面DOM树解析不受影响。
 

结论:

  1. css的加载不会阻塞DOM的解析
  2. css的加载阻塞DOM的渲染
  3. JS的加载和执行会阻塞DOM的解析
  4. JS的加载和执行会阻塞DOM的渲染
  5. css加载会阻塞后面js语句的执行

根据原理进行渲染优化:

1、尽可能早的提前引入css文件,例如在head部分引入css文件。(因为CSS解析和DOM解析可以同时进行,所以CSS资源放在头部不会影响DOM解析,而且放在头部也会优先开始加载CSS样式,在渲染DOM的时候也已经知道了自己的样式,所以一次就可渲染成功,如果将CSS放在底部,那么会优先渲染DOM,而浏览器为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容,也就是说渲染引擎会边解析、边渲染、边布局显示,已尽快减少白屏时间,所以随着CSS规则树的构建,还需要对之前渲染树重新渲染,可能会导致回流和页面跳动。)

2、尽可能早的加载css文件中的引入的资源,例如自定义字体文件,可以使用预加载,在link标签中加入rel=“preload” as = “font”该元素属性,不会造成渲染阻塞。

3、在DOM和CSS渲染之后加载js文件,例如在尾部加载js文件,或者使用该元素属性defer和async,进行js问价异步加载,但是不同的浏览器会有兼容性问题。(因为JS在运行时,如果需要操作CSS,但该CSS还没有下载和构建,则首先会阻塞JS线程,然后开启新线程去下载解析构建CSS规则树,再执行JS代码)

4.JS资源尽量放在body结束标签之前(JS放在body标签结束之前,首先可以确保能取到需要操作的DOM对象,也可缩短因JS阻塞。而造成的白屏时间,提升用户体验。因为如果把JS放在head部分,JS运行会阻塞DOM树和CSS树构建,导致白屏时间延长,影响用户体验。)

优化策略

浏览器本身的优化策略:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列。

减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略

方法:
1. 将多次改变样式属性的操作合并成一次操作。
2.将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。

3. 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。

4. 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。

5. 在需要经常取那些引起浏览器重排的属性值时,要缓存到变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值