导航流程
浏览器从输入URL
到页面展示间到底发生了什么?这整个流程涉及到网络请求和渲染流程两大块内容。我们可以先看一下总体的导航流程。
浏览器进程构建完整的URL
- 浏览器进程会检查输入的URL,组装协议,构建完整的的URL
- 浏览器进程会通过进程间通信(IPC)把URL请求发送网络进程
网络进程发起URL请求
- 检查本地缓存是有效,如果有有效,则使用本地缓存;如果无效,则进入网络请求流程
- 网络请求的第一步:DNS解析,获取请求域名的IP地址
- 和服务器建立TCP链接,并且构建请求信息
- 服务器接收到请求之后,会构建响应信息
- 浏览器接收到响应信息后,网络请求会解析响应信息, 若状态码是301/302,则会重定向到新的地址,重新发起URL请求
- 浏览器根据响应的类型(ConTent-Type)进行处理
浏览器进程向渲染进程提交文档
- 浏览器接收到响应信息后, 会开始准备渲染进程, 同一个站点(同根域名,同协议)会复用同一个渲染进程
- 浏览器进程准备完毕后,浏览器进程会和渲染进程进行通信,传输文档
渲染进程开始解析页面和加载子资源,完成页面的渲染
这个过程就是渲染流程,就是我们本文重点讲的
渲染流程
构建DOM树
为什么要构建DOM树呢?因为浏览器无法识别HTML,所以需要HTML解析成浏览器识别的数据结构——DOM树。
构建CSSOM树
一样的,浏览器也无法识别CSS,所以浏览器会先将CSS解2.jpg析成浏览器能识别的数据结构—styleSheets
然后浏览器会将属性值转化成标准值,因为标准值才容易被渲染引擎理解和使用。
最后,计算DOM树˙中的每个节点的演示, 生成最终的CSSDOM树,这个过程涉及到CSS的继承规则和层叠规则。
构建渲染树(Render Tree),并计算布局
将DOM树和CSSDOM树结合就可以得到渲染树。
首先,浏览器会遍历所有可见的元素(像hea这类不可见的标签或者display设置为none的元素等会被排除在外)。
接着,找到节点所适配的的样式并应用, 最终生成渲染树。
即使现在有了渲染树,但是浏览器不知道每个节点的位置信息,所以浏览器会遍历渲染树,计算每个节点的位置信息,这就是计算布局。
分层和绘制
经过计算布局之后,并不是立马进行绘制,而是会为有3D或透视变换、z轴排序等复杂效果的节点创建图层,并生成图层树,这样做的目的是方便地实现复杂效果。浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
什么情况下会创建图层呢?
- 有层叠上下文(明确定位属性的元素, 定义透明属性的元素,使用CSS 滤镜)
- 需要裁剪(overflow: hidden;溢出部分被剪裁)
有了图层树之后,接下来就是绘制图层了。
接着,浏览器会将图层划分为图块,这么做的目的是因为视口显示的内容有限,如果直接将整个结构进行绘制开销比较大,所以浏览器会优先将视口内的图块转为位图,这个过程叫栅格化。
最后,将位图合成,浏览器开始显示。
小结
如上图所示,HTML的渲染过程如下:
将HTML解析为DOM树
将CSS解析为CSSOM树
将DOM树和CSSOM树构建成渲染树,并计算布局
进行分层和绘制
渲染进程的特点
回流和绘制
回流和绘制是渲染进程比较重要的概念了,了解其中的概念并进行合理的的应用,可以提升性能。
回流
当元素的几何属性(尺寸),隐藏属性等改变而触发重新布局的渲染,这个过程就是回流。回流需要更新完整的渲染流程(布局-分层-绘制-图块-栅格化-合成-显示), 所以开销比较大,需要尽量避免.
触发回流的属性
- 盒子模型相关属性(width、padding、margin、display、border等)
- 定位属性和浮动(position、top、float等)
- 文字结构(text-align、font、white-space、overflow等)
重绘
当元素的外观、风格等属性发生改变但不会影响布局的渲染,这个过程就是重绘。重绘省去了布局和分层阶段(绘制-图块-栅格化-合成-显示),所以性能比回流要好。回流必将引起重绘,重绘不一定会触发回流。
触发重绘的属性
color,border-style, background,outlinee,box-shadow,visibility, text-decoration
避免回流和重绘
频繁触发重绘和回流,会导致UI频繁渲染,最终导致性能变差。所以要尽量避免重绘和回流:
- 避免使用触发重绘和回流的CSS属性
- 将频繁重绘回流的元素创建为一个独立图层
技巧
- 使用transform实现效果:可以避开回流和重绘,直接进入合成阶段(图块-栅格化-合成-显示)
- 用opacity替代visibility:visibility会触发重绘
- 使用class替代DOM频繁操作样式
- DOM离线后修改,如果有频繁修改,可以先把DOM隐藏,修改完成后再显示
- 不要在循环中读取DOM的属性值:offsetHeight会使回流缓冲失效
- 尽量不要使用table布局,小改动会造成整个table重新布局
- 动画的速度:200~500ms最佳
- 对动画新建图层
- 启用GPU硬件加速:启用translate3D
HTML解析的特点
顺序执行,并发加载
- 顺序执行:HTML的词法分析事从上到下, 顺序执行
- 并发加载:当HTML解析被脚本阻碍时,解析器虽然会停止构建DOM,但仍会识别脚本后面的内容,并进行加载
- 并发上限:浏览器对同域名的并发数是有限制的
阻塞
css阻塞
- css 在head中阻塞页面的渲染:避免页面闪动
- css会阻塞jsd的执行:CSSOM构建的时候,Javascript执行将被暂停,知道CSSDOM构建完成
- css不会阻塞外部脚本的加载
js阻塞
- 直接引入的js会阻塞页面的渲染:当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行
- js不阻塞资源的加载
- js顺序执行,会阻塞后续js的执行
- js可以查询和修改 DOM 与 CSS
改变js的阻塞
defer
和async
属性可以改变js的阻塞情形,不过这两个只对src方式引入的script有效,对于inline-script无效。
defer
表示延迟执行,浏览器会异步地加载该脚本并且不会影响到后续DOM的渲染,该脚本将在文档完成解析后,DOMContentLoaded
事件触发前执行。对动态嵌入的脚本使用 async=false
来达到类似的效果。
async
表示异步执行,浏览器会异步地加载脚本并在允许的情况下执行。与 defer 的区别在于,无论是 HTML 解析阶段还是DOMContentLoaded
触发之后,如果脚本加载完成,就会开始执行。需要注意的是,这种方式加载的 JavaScript 依然会阻塞load
事件。