浏览器的渲染原理
# 浏览器的渲染原理
# 简述一下浏览器的渲染过程重要
首先解析收到的文档,根据文档的定义构建一棵
DOM
树,DOM
树是由DOM
元素及属性节点组成的然后对
css
进行解析,生成CSSOM
树根据
DOM
树和CSSOM
树构建render
树,渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和DOM
元素相对应,但是不可见的DOM
元素不会被插入render
树上,还有一个dom
元素对应几个可见对象,是一些具有复杂结构的元素,无法用矩形来描述当渲染对象被创建并添加到树中,他们没有位置和大小,所以当浏览器生成
render
树之后,会根据render
树进行布局(也可以叫回流),此时浏览器要弄清楚各个节点在页面的确切位置和大小,也被称为自动重排布局结束后是绘制阶段,遍历渲染树并调用渲染对象的
paint
方法将内容显示在屏幕上,绘制使用UI
基础组件
注意
这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html
都解析完成之后再去构建和布局 render
树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容
# 介绍一下浏览器的渲染优化
针对
JS
:js
会阻塞html
解析,也会阻塞css
解析- 尽量将
js
文件放在body
最后 body
中间尽量不要写script
标签script
的三种引入方式<script>
立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行js
代码,js
代码执行完毕后继续渲染页面;async
是在下载完成之后,立即异步加载,加载好后立即执行,多个带async
属性的标签,不能保证加载的顺序;defer
是在下载完成之后,立即异步加载。加载好后,如果DOM
树还没构建好,则先等DOM
树解析好再执行;如果DOM
树已经准备好,则立即执行。多个带defer
属性的标签,按照顺序执行。
- 尽量将
针对
CSS
:使用css
的方法:link
@import
内联样式link
:浏览器会派发一个线程(http
线程)去加载资源文件,同时GUI
渲染线程会继续向下渲染代码@import
:GUI
渲染线程会暂停渲染,去服务器加载资源文件,资源文件没返回之前不会继续渲染(阻碍浏览器渲染)内联样式:
GUI
线程直接渲染注意⚠️
外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以
CSS
一般写在header
中,让浏览器尽快发送请求去获取css
样式。所以,在开发过程中,导入外部样式使用link
,而不用@import
。如果css
少,尽可能采用内嵌样式,直接写在style
标签中
针对
DOM
树CSSOM
树html
文件代码层级尽量不要太深- 使用语义化标签,避免不标准语义化的特殊处理
- 减少
css
代码的层级,因为选择器是从右向左进行解析的
减少回流和重绘
- 操作
dom
时,尽量在底层级的dom
节点进行操作 - 不要使用
table
,一个小的改动会使整个table
进行重新布局 - 不能频繁操作元素的样式,对于静态页面,尽量修改类名,而不是样式
- 使用
css
表达式 - 使用
absolute
或者fixed
,使元素脱离文档流,这样发生变化不会影响其他元素 - 避免频繁操作
dom
,可以创建一个文档片段,documentFragment
,在他上面应用所有dom
操作,然后把他添加到文档中 - 将元素先
display:none
,操作结束后再把他显示出来,因为display
为none
中进行dom
操作不会引发回流和重绘 - 将
dom
的多个读写操作放在一个,而不是读写操作穿插,得益于浏览器的渲染队列机制
值得注意的是⚠️
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。 将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。
- 操作
# 渲染过程中遇到的js
文件如何处理
js
的加载、解析和执行会阻塞文档的解析,也就是说在构建dom
是,html
解析器遇到js
的话会暂停文档的解析,然后将控制权移交给JS
引擎,等他完成之后浏览器再从中断等地方恢复继续解析文档,也就是说,如果想要首屏渲染地越快,就越不应该再首屏加载JS
文件,还是建议把script
放到body
的底部,或者加上defer
或async
# 什么是文档的预解析
webkit
和firefox
都做了优化,当执行JS
脚本的时候,另一个线程解析剩下的文档并加载后面需要用到的网络资源,这种方式可以使资源并行加载从而使整体速度更快,值得注意的是预解析并不改变dom
树,他将整个工作留给了主解析过程,自己只解析外部资源的引用,比如外部script
、css
、image
# CSS
如何阻塞文档解析
理论上即使css
不改变dom
树,也就没有必要停下文档的解析等待他们,然而当JS
执行时可能在文档解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然会导致很多问题,所以如果浏览器尚未完成 CSSOM
的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript
脚本执行和文档的解析,直至其完成 CSSOM
的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM
,然后再执行 JavaScript
,最后再继续文档的解析
# js
脚本放在头部一定会造成阻塞吗
不一定会
如果js
并没有引用dom
的话就不会造成阻塞,当然也不排除其他情况阻塞,但是并不会一定造成阻塞
# 如何优化关键渲染路径
为了尽快完成首次渲染,需要最大限度减小以下三种可变因素
关键资源的数量
关键路径长度
关键字节的数量
关键资源是可能阻止网页首次渲染的资源。
这些资源越少,浏览器的工作量就越小,对 CPU
以及其他资源的占用也就越少。同样,关键路径长度受所有关键资源与其字节大小之间依赖关系图的影响:某些资源只能在上一资源处理完毕之后才能开始下载,并且资源越大,下载所需的往返次数就越多。
最后,浏览器需要下载的关键字节越少,处理内容并让其出现在屏幕上的速度就越快。要减少字节数,我们可以减少资源数(将它们删除或设为非关键资源),此外还要压缩和优化各项资源,确保最大限度减小传送大小。
优化关键渲染路径的常规步骤如下:
- 对关键路径进行分析和特性描述:资源数、字节数、长度。
- 最大限度减少关键资源的数量:删除它们,延迟它们的下载,将它们标记为异步等。
- 优化关键字节数以缩短下载时间(往返次数)。
- 优化其余关键资源的加载顺序:您需要尽早下载所有关键资源,以缩短关键路径长度
# 什么情况下会阻塞渲染
首先渲染的前提是生成render
树,所以html
和css
肯定会阻止渲染,如果想更快,那只能降低文件大小或者压缩,扁平层级,优化选择器。然后当浏览器解析到script
标签时,会暂停构建DOM
,完成之后才会从暂停的地方重新开始,所以想更快,就不应该在首屏加载JS
文件,全是建议在body
标签底部写入script
标签的
或者给script
标签加上defer
或者async
属性,都会并行下载,同时不会阻塞渲染