介绍
如果把浏览器呈现页面的整个过程一分为二,一部分是浏览器为呈现页面请求所需资源的部分,另一部分是浏览器获取到资源后,进行渲染部分的相关优化内容。
在关键渲染路径中,浏览器通过这个过程对 HTML、CSS、JavaScript 等资源文件进行解析,然后组织渲染出最终的页面。
浏览器从获取 HTML 到最终在屏幕上显示内容需要完成以下步骤:
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个 render tree。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
经过以上整个流程我们才能看见屏幕上出现渲染的内容,优化关键渲染路径就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间,让用户最快的看到首次渲染的内容。
不但网站页面要快速加载出来,而且运行过程也应更顺畅,在响应用户操作时也要更加及时,比如我们通常使用手机浏览网上商城时,指尖滑动屏幕与页面滚动应很流畅,拒绝卡顿。那么要达到怎样的性能指标,才能满足用户流畅的使用体验呢?
目前大部分设备的屏幕分辨率都在60fps左右,也就是每秒屏幕会刷新60次,所以要满足用户的体验期望,就需要浏览器在渲染页面动画或响应用户操作时,每一帧的生成速率尽量接近屏幕的刷新率。若按照60fps来算,则留给每一帧画面的时间不到17ms,再除去浏览器对资源的一些整理工作,一帧画面的渲染应尽量在10ms内完成,如果达不到要求而导致帧率下降,则屏幕上的内容会发生抖动或卡顿。
为了使每一帧页面渲染的开销都能在期望的时间范围内完成,就需要开发者了解渲染过程的每个阶段,以及各阶段中有哪些优化空间是我们力所能及的。经过分析根据开发者对优化渲染过程的控制力度,可以大体将其划分为五个部分:JavaScript处理、计算样式、页面布局、绘制与合成,下面先简要介绍各部分的功能与作用。
- JavaScript 处理:前端项目中经常会需要响应用户操作,通过 JavaScript 对数据集进行计算、操作 DOM 元素,并展示动画等视觉效果。当然对于动画的实现,除了 JavaScript,也可以考虑使用如 CSS Animations、Transitions 等技术。
- 计算样式:在解析 CSS 文件后,浏览器需要根据各种选择器去匹配所要应用 CSS 规则的元素节点,然后计算出每个元素的最终样式。
- 页面布局:指的是浏览器在计算完成样式后,会对每个元素尺寸大小和屏幕位置进行计算。由于每个元素都可能会受到其他元素的影响,并且位于 DOM 树形结构中的子节点元素,总会受到父级元素修改的影响,所以页面布局的计算会经常发生。
- 绘制:在页面布局确定后,接下来便可以绘制元素的可视内容,包括颜色、边框、阴影及文本和图像。
- 合成:通常由于页面中的不同部分可能被绘制在多个图层上,所以在绘制完成后需要将多个图层按照正确的顺序在屏幕上合成,以便最终正确地渲染出来。
这个过程中的每一阶段都有可能产生卡顿,然而并非对于每一帧画面都会经历这五个部分。比如仅修改与绘制相关的属性(文字颜色、背景图片或边缘阴影等),而未对页面布局产生任何修改,那么在计算样式阶段完成后,便会跳过页面布局直接执行绘制。
加载过程中的优化
我们看一下HTML页面加载渲染的过程。
上面这个图就是HTML渲染成页面的一个过程,首先HTML会渲染成dom树,HTML文档是我们最先通过网址请求回来的,然后浏览器的html parser对其进行解析,词法分析之后,将语法分析成相对应的token并将其添加到dom树中,所以HTML会从上往下进行对文档进行词法分析,所以分析过程中通过link、script等方式引入其他的资源,例如CSS,然后浏览器会请求对应资源,然后再对CSS进行解析生成CSSOM,最终会与dom树生成渲染树,然后进行布局、绘制,这就是整个流程。
HTML渲染的一些特点:
- 顺序执行、并发加载:通过词法分析,通过HTML生成令牌对象(当前节点的所有子节点生成后,才会通过下一个令牌获取到当前节点的兄弟节点),最终生成Dom树浏览器中可以支持并发请求,不同浏览器所支持的并发数量不同(以域名划分),以chrome为例,并发上限为6个。优化点:把CDN资源分布在多个域名下
- 是否阻塞:CSS层面,CSS放在头中会阻塞页面的渲染(也就是说页面的渲染会等到CSS加载完成),CSS阻塞JS的执行(因为GUI线程和JS线程是互斥的,因为有可能JS会操作CSS),CSS不阻塞外部脚本的加载(不阻塞JS的加载,但阻塞JS的执行,因为浏览器都会有预先扫描器)。JS层面,直接引入的JS会阻塞页面的渲染(GUI线程和JS线程互斥),JS不阻塞资源的加载(这有赖于chrome的预加载机制),JS顺序执行,阻塞后续JS逻辑的执行
- 依赖关系:页面渲染依赖于CSS的加载,JS的执行顺序的依赖关系,JS逻辑对于Dom节点的依赖关系
- 引入方式:JS,直接引入,推迟(不阻塞页面渲染,顺序执行),异步(不阻塞页面渲染,不保证顺序执行),异步动态引入JS
优化点:css 样式表置顶,用 link 代替 import,js 脚本置底,合理使用 js 的异步加载能力。