浏览器渲染优化

1,280 阅读7分钟

小白时期

在业务经验不足的时候,对Web性能的理解是笼统的,了解的优化方式也是零零散散,阅读了一些性能优化的标准,如 雅虎军规35条,却总是觉得不够系统。要构建完整的知识体系,跟零散的知识点say no,就需要系统地了解一下前端性能。

原则

在诊断和优化网页性能之前,我们首先要明确一个原则,即工具大于规则。

  1. 工具比规则重要,规则很重要,但是会随时改变,因此,熟悉手头现有的工具更加重要,因为这些工具可以让你分析应用的性能。
  2. 数据会变化,bug 会修复。
  3. 理解了这个工具大于规则的原则之后,使你能够首先去衡量性能,经常衡量,而衡量的结果是理解性能的关键。你应该通过衡量来了解你遇到的问题,然后继续衡量,判断你的更改是否真的解决了问题。

Tools not Rules

正确打开姿势

谷歌开发者文档 关于性能的部分,提到了用于衡量性能的标准—RAIL模型,并且,从中可以看到 Web性能的优化大致分为2部分:加载性能和渲染性能。

谷歌开发者文档—性能

一张Web性能优化参考图

1. App生命周期(App Lifecycles)

APP生命周期

说到RAIL模型,跟网络应用的生命周期是分不开的。

任何网络应用的生命周期实际上分为四大领域,每个领域对性能的影响都有很大的差别。我们将网络应用的 生命周期四大领域 称为 RAIL(or LIAR),即 load(加载)idle(闲置)animations(动画)responsiveness(响应)

Load:无论是什么内容,用户都希望快速加载,一定要对关键呈现路径做出优化。在加载阶段,你有大概1秒钟即1000毫秒的时间来呈现网页,避免应用变得没有响应而导致用户的关注级别下降。这时候就需要下载和渲染关键资源。

Idle:加载之后,应用进入闲置状态,等着用户采取操作,这时候最适合执行不太重要的工作,确保在此之后出现的任何互动都能及时做出响应。通常应用的闲置时间约为50毫秒(虽然可能会一次出现多个闲置状态),以便当用户开始互动时能够停止闲置状态。

在处理了加载任务,并考虑了闲置时间可以执行的操作,接下来就是用户与应用互动的阶段,因此需要没有延迟地对用户输入操作做出响应。那么响应程度需要达到什么级别呢?

Response:研究表明,在响应阶段,人类大脑可以忍受100毫秒的停顿时间,再长时间的延迟就会让人觉得卡顿不流畅,这就意味着应用需要以某种方式在100毫秒内对用户输入操作做出响应。明智地使用这段时间非常关键,这样才能设置动画,使其达到60帧/秒。

Amimate:在动画阶段,例如用户滚动屏幕或出现动画,对应用的响应更具挑战性,你只有16毫秒(实际上只有10毫秒)的时间来渲染一帧,这时候60帧/秒的帧率非常关键。

2. 衡量和诊断性能(Weapons of Jank Destruction)

如果一个页面首屏渲染较慢,操作卡顿、不流畅等等,这是给我们衡量这个页面性能好坏的直观体现,而真正具体到依据层面,就需要用到 RAIL 模型。RAIL 模型给出了 App 在四大生命周期中的不同指标( 使用 RAIL 模型评估性能)。

RAIL

那么,指标有了,如何来诊断呢?最常用的还是 Chrome Dev Tools。Chrome 为我们提供了Performance 来获取我们的页面渲染性能数据,其诊断也是按照 RAIL 模型给出的数据。具体使用,请参考:全新Chrome Devtool Performance 使用指南。还有一些功能,如 JavaScript Profiler、Rendering、Layers 等都能帮助我们更好的诊断页面性能。

Chrome Dev Tools

3. 关键路径渲染(The Critical Rendering Path)

像素管道的关键点

通过关键路径渲染,我们可以知道浏览器在呈现一个Web页面时都经历了哪些步骤?创建每个帧时都涉及到哪些内容?

(1) 浏览器向服务器发送 GET 请求

GET / HTTP/1.1

(2)服务器返回 html 时,浏览器会进行提前解析,它会解析文档并向我们提供节点,称为 DOM 树

浏览器解析 html

生成DOM树

(3)DOM 和 CSS 结合,获得新的渲染树

结合CSS重新计算样式

渲染树看起来和DOM树非常相似,只是缺少了某些内容,例如没有了 head,也没有任何 script 脚本,并且如果在 CSS 中设置了某些样式,则最终的渲染树会根据样式而呈现,如:

  • 如果有 CSS 将部分段落设置 display: none,那么段落会从渲染树中移除;
  • 如果CSS添加了伪元素,例如 after 或 before,就会添加到渲染树中,虽然没有出现在 DOM 中。
section p { display: none; }  // p节点会从渲染树中移除
section h1:after {  // 该伪类会添加到渲染树中,即使它没有出现在 DOM 中
    content: "I <3 pseudo elmz";
}

渲染树构建

注意:只有实际上会显示在网页上的元素,才会进入渲染树。

(4)计算布局

回到单个帧的渲染过程,一旦浏览器知道哪个规则适用于相关元素之后,就开始计算布局,也就是计算元素会占用多少空间、位于屏幕的什么位置。

计算布局

网络布局模型(layout model)意味着某个元素可以影响到其他元素,例如 body 的宽度通常会影响到其子节点的宽度,一直延伸到树的下方。所以这一流程对浏览器来说可能非常复杂,有时候你可能会听到布局也叫做回流,其实是同一概念。

(5)绘制

布局的下一步是从矢量到光栅的转换过程,原先生成的布局是矢量图,绘制到浏览器上,便是填充像素的过程,需要光栅器执行一系列的绘制调用。

将矢量填充到每个像素

也就是说,绘制实际上分为两个任务:1)创建绘图调用的列表;2)填充像素,即“栅格化”。因此,当在 DevTools 中看到绘制记录时,就应当视其为包括栅格化。 (在某些架构下,绘图调用的列表创建以及栅格化是在不同的线程中完成,但是这不是开发者所能控制的。)

绘制之前

全部调用,绘制完成之后的效果

在上面的绘制调用列表中,有个调用叫做绘制位图(15. drawBitmap),通常我们会通过网络向网页发送 JPEG、PNG 或 GIF 等内容,而浏览器需要将这些内容解码成内存:

绘制位图

图片解码+大小调整

(6)合成

上面的流程,你可能注意到 Painting 是在一个层面完成的,但是,浏览器有时候会创建多个层面,叫做图层或合成层,并且可以单独绘制这些图层。

合成层

实际上,绘制是在如下的网格图块中实现的:

网格图块

我们可以完整地描述整个渲染过程,但是我们开发者无法控制这个过程,所有这些都是在CPU上发生的,图层本身和其图块将上传到GPU中。

图层上传到GPU

最后GPU将根据指示将图片显示到屏幕上,这就是从单个请求一直到将像素填充到屏幕上的简单流程。

流程结束

提升为合成层的好处如下:

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

总结

工欲善其事,必先利其器。了解了性能优化的原则、衡量指标和工具、以及页面渲染的原理之后,我们就能有针对性地对页面展开性能优化了。具体的优化途径,请继续关注哦😄。

参考链接