浏览器的渲染过程
浏览器渲染主要有以下步骤:
- 首先解析收到的文档,根据文档定义构建一颗DOM树,DOM树是由DOM元素及属性节点组成的
- 然后对CSS进行解析,生成CSSOM规则树
- 根据DOM树和CSSOM规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和DOM元素相对应,但这种对应关系不是一对一的,不可见的DOM元素不会插入到渲染树。还有一些DOM元素对应几个可见对象,它们一般是一些具体复杂结构的元素,无法用一个矩形来描述。
- 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(又称回流)。这一阶段浏览器要做的事情就是要弄清各个节点在页面中的确切位置和大小。通常这一行为又称为“自动重排”
- 布局阶段结束后是会绘制阶段,遍历渲染树并调用渲染对象的paint方法,将它们的内容显示在屏幕上,绘制使用UI基础组件。
注意:这个过程是逐步完成的,为了更好的用户体验,渲染引擎会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容
浏览器渲染优化
(1) 针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化:
- 尽量将JS文件放到body的最后
- body中间尽量不要写
<script>
标签 <script>
标签的引入资源方式由三种,有一种就是我们常用的直接引入,还有两种就是使用async属性和defer属性来异步引入,两者都是去异步加载外部的JS文件,不会阻塞DOM的解析- script立即停止页面渲染去加载资源文件,当资源加载完毕后立刻执行JS代码,JS代码执行完毕后继续渲染页面
- async是在下载完成之后,立即异步加载,加载好后立即执行,多个带async属性的标签,不能保证加载的顺序
- defer是在下载完成之后,立即异步加载。加载好后,如果DOM树还没构建好,则先等DOM树解析好后再执行,如果DOM树已经准备好,则立即执行。多个带defer属性的标签,按照顺序执行
(2) 针对CSS:使用CSS有三种方式:使用link,@import,内联样式
- link:浏览器会派发一个新等线程(HTTP线程)去加载资源文件、与此同时GUI渲染线程会继续向下渲染代码
- @import:GUI渲染线程会暂时停止渲染,去服务器加载资源文件、资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)
- style:GUI直接渲染
外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器默认样式,确保首次渲染的速度。所以CSS一般写在header中,让浏览器尽快发送请求去获取CSS样式
所以在开发过程中,导入外部样式使用link,而不用@import。如果CSS少,则尽可能采用内嵌样式,直接写在style标签中
(3)针对DOM树、CSSOM树:
- HTML文件的代码层级尽量不要太深
- 使用语义化的标签,来避免不标准语义化的特殊处理
- 减少CSSD代码的层级,因为选择器是从左向右进行解析的
(4)减少回流和重绘
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要使用
table
布局,一个小的改动可能会使整个table
重新布局 - 使用CSS的表达式
- 不要频繁操作元素的样式,对于静态页面可以修改类名而不是样式
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免频繁操作DOM,可以创建一个文档片段
documentFragment
,在它上面应用所有DOM操作,最后再把它添加到文档中 - 将元素先设置为
display:none
,操作结束后再把它显示出来,因为在display属性为none的元素上进行DOM操作不会引发回流和重绘 - 将DOM的多个读(写)操作放在一起,而不是读写操作穿插着写,这得益于浏览器的渲染队列机制 浏览器会将所有的回流、重绘的操作放在一个队列中,当队列的操作到了一定的数量或者到了一定时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流、重绘