文章目录
浏览器渲染原理
(一)、两个阶段
用户看到页面分为两个阶段:
- 页面内容加载完成(DOMContentLoaded);
- 页面资源加载完成(load);
(二)、五个步骤
浏览器渲染的过程分为五个步骤:
- 浏览器获取HTML文档解析成DOM树
- 处理CSS标记,解析CSS文件,构成层叠样式表模型CSSOM
- 将DOM和CSSOM合并为渲染树(Render Tree),代表一系列将被渲染的对象
- 进行元素布局(layout),从根节点开始
- 将各节点绘制(painting)到屏幕上,调用渲染器的
paint()
方法
(三)、解析HTML文档过程
- 解析HTML文档是一行一行读HTML代码的,包括了
<script>
标签,所以DOM树在构建过程中可能会被CSS和JS的加载而阻塞 display:none
的元素和script
标签也会在DOM树种
(四)、DOM树和渲染树(Render Tree)
- 浏览器从DOM树的根节点开始遍历每个可见节点,然后根据CSS规则树找到每个节点适配的CSS样式规则并应用从而构成渲染树
- 渲染树与DOM树不完全对应:
display: none
的元素不在Render Tree中visibility: hidden
的元素在Render Tree中
(五)、渲染阻塞
(script标签和css样式在文档中的位置)
1.JS阻塞
浏览器在构建DOM树的时候,遇到<script>
标签的时候DOM构建将会暂停直到脚本完全执行再继续构建DOM;如果是外部脚本,则会等脚本下载完毕再继续构建DOM树。
2.CSS阻塞
由于CSSOM负责存储渲染信息,再合成渲染树之前应该完备(CSS内联、内部、外部都下载完并解析完),只有CSSOM和DOM的解析完全结束浏览器才会进入下一步的渲染,这就是CSS阻塞。
3.减小阻塞
<script>
标签位于body
标签底部,减少First Paint
(解析并显示部分内容)时间,可以尽快的减少白屏事件,但是不会较少DOMContentLoaded
被触发的事件- 在
<script>
标签上增加属性defer
或者async
defer
:异步下载js文件不会影响后续DOM渲染,脚本会在DOM渲染完毕后,DOMContentLoaded事件调用前执行,脚本按顺序执行。
(适用于依赖DOM元素的脚本比如评论框。)async
:使脚本异步的加载并在允许的情况下执行,不影响DOMContentLoaded事件的调用,先加载完的脚本先执行。
(适用于不关心DOM元素的脚本。)
- CSS样式放在
head
中,尽快解析CSS保证更快的First Paint
,CSSOM完毕之前页面一直处于白屏状态。
(六)、回流和重绘
1.回流(reflow)
浏览器布局发生变化需要重新渲染。这个回退过程叫做回流,回流会从html的根节点root frame开始递归往下。
- 优化:异步reflow或增量异步reflow,浏览器积攒到足够数量的变化后再做一次reflow。
- 引起reflow:
- 页面第一次渲染
- DOM树变化(如增删节点)
- Render树变化(如padding改变)
- 获取元素的某些属性
- 元素CSS设置display:none
2.重绘(repaint)
当改变某个元素的背景色、文字颜色、边框颜色等不影响周围或内部布局的属性的时候,屏幕一部分需要重画但是布局没有发生改变。
- 引起repaint:
- 背景色、颜色、字体改变(字体大小发生变化时,会触发回流)
reflow回流必定引起repaint重绘,重绘可以单独触发
3.优化
(减少reflow、repaint触发次数)
- 用transform做形变和位移可以减少reflow
- 避免逐个修改节点样式,尽量一次性修改
使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染 - 可以将需要多次修改的DOM元素设置display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
- 避免多次读取某些属性
- 通过绝对位移将复杂的节点元素脱离文档流,形成新的Render Layer,降低回流成本
(七)、优化渲染效率
- 样式文件在
head
标签中,脚本文件在body
结束前,可以防止渲染阻塞 - 简化并优化CSS选择器
- DOM多个操作放在一起
- 不一条一条的改变样式,而是通过改变
class
一次性改变样式。 - 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。