Search K
Appearance
Appearance
在之前的文章里面分析了页面加载和 DOM 生成,讨论了 JavaScript 和 CSS 是如何影响到 DOM 生成的,还结合渲染流水线来讲解了分层和合成机制。接下来将讨论页面优化的问题,其实就是要让页面更快地显示和响应。由于一个页面在它不同的阶段,所侧重的关注点是不一样的,所以如果要讨论页面优化,就要分析一个页面生存周期的不同阶段。
通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。
这里我们需要重点关注加载阶段和交互阶段,因为影响到我们体验的因素主要都在这两个阶段
并非所有的资源都会阻塞页面的首次绘制,比如图片、音频、视频等文件就不会阻塞页面的首次渲染;而 JavaScript、首次请求的 HTML 资源文件、CSS 文件是会阻塞首次渲染的,因为在构建 DOM 的过程中需要 HTML 和 JavaScript 文件,在构造渲染树的过程中需要用到 CSS 文件。
这些能阻塞网页首次渲染的资源被称为关键资源。基于关键资源,我们可以继续细化出来三个影响页面首次渲染的核心因素。
关键资源个数。关键资源个数越多,首次页面的加载时间就会越长。比如上图中的关键资源个数就是 3 个,1 个 HTML 文件、1 个 JavaScript 和 1 个 CSS 文件
关键资源大小。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短。上图中关键资源的大小分别是 6KB、8KB 和 9KB,那么整个关键资源大小就是 23KB。
请求关键资源需要多少个 RTT(Round Trip Time)
总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数。
接下来再来聊聊页面加载完成之后的交互阶段以及应该如何去优化。谈交互阶段的优化,其实就是在谈渲染进程渲染帧的速度,因为在交互阶段,帧的渲染速度决定了交互的流畅度。因此讨论页面优化实际上就是讨论渲染引擎是如何渲染帧的,否则就无法优化帧率。
交互阶段的渲染流水线(如下图)。和加载阶段的渲染流水线有一些不同的地方是,在交互阶段没有了加载关键资源和构建 DOM、CSSOM 流程,通常是由 JavaScript 触发交互动画的。
结合上图,回顾下交互阶段是如何生成一个帧的。大部分情况下,生成一个新的帧都是由 JavaScript 通过修改 DOM 或者 CSSOM 来触发的。还有另外一部分帧是由 CSS 来触发的。如果在计算样式阶段发现有布局信息的修改,那么就会触发重排操作,然后触发后续渲染流水线的一系列操作,这个代价是非常大的。
同样如果在计算样式阶段没有发现有布局信息的修改,只是修改了颜色一类的信息,那么就不会涉及到布局相关的调整,所以可以跳过布局阶段,直接进入绘制阶段,这个过程叫重绘。不过重绘阶段的代价也是不小的。
还有另外一种情况,通过 CSS 实现一些变形、渐变、动画等特效,这是由 CSS 触发的,并且是在合成线程上执行的,这个过程称为合成。因为它不会触发重排或者重绘,而且合成操作本身的速度就非常快,所以执行合成是效率最高的方式。
回顾了在交互过程中的帧是如何生成的,那接下来就可以讨论优化方案了。一个大的原则就是让单个帧的生成速度变快。所以,下面就来分析下在交互阶段渲染流水线中有哪些因素影响了帧的生成速度以及如何去优化。
有时 JavaScript 函数的一次执行时间可能有几百毫秒,这就严重霸占了主线程执行其他渲染任务的时间。针对这种情况我们可以采用以下两种策略:
合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同,如果主线程被 JavaScript 或者一些布局任务占用,CSS 动画依然能继续执行。所以要尽量利用好 CSS 合成动画,如果能让 CSS 处理动画,就尽量交给 CSS 来操作。另外,如果能提前知道对某个元素执行动画操作,那就最好将其标记为 will-change,这是告诉渲染引擎需要将该元素单独生成一个图层。
我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。所以要尽量避免产生那些临时垃圾数据。那该怎么做呢?可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。