内容简介:天下武功唯快不破,页面加载速度与用户体验息息相关,也关乎企业收益:最知名的当属 Amazon 加载速度每增加一秒一年少赚 16 亿美刀的例子(还是 N 年前的统计),真是应验那句话:时间 == 金钱!对此 Google Developer 专门有一篇
天下武功唯快不破,页面加载速度与用户体验息息相关,也关乎企业收益:最知名的当属 Amazon 加载速度每增加一秒一年少赚 16 亿美刀的例子(还是 N 年前的统计),真是应验那句话:
时间 == 金钱!
对此 Google Developer 专门有一篇 文章 用于阐述页面载入速度的重要性,此外还专门做了个 工具 ,用于展示页面加载速度与收益关系。除了说,他们也这么做了: Google 直接把页面速度纳为搜索排名的指标。
优化页面加载速度环节众多,今天要说的是关键渲染路径(critical rendering path)优化,及缩短首次渲染页面的时间。
浏览器如何呈现页面的
知己知彼,百战不殆。我们先得了解页面渲染过程,进而从中寻找出优化环节。
DOM
以下面的简单页面为例:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
浏览器如何处理页面流程: 字节 → 字符 → 令牌 → 节点 → 对象模型 ,最终得到的是文档对象模型 (DOM)。
- 转换 : 浏览器读取 HTML 原始字节,根据指定编码转化为字符
- 令牌化 :将字符转化为 token 以及字符串
- 词法分析 :将 token 转化为定义其属性与规则的 node
- DOM 构建 :将 node 连接在一起组成一颗树
DOM 树捕获文档标记的属性和关系,至于如何定义外观样式。那是 CSSOM 的责任。
CSS
浏览器处理 CSS 过程: 字节 → 字符 → 令牌 → 节点 → CSSOM 。
浏览器在构建 DOM 时,遇到了一个引用有 style.cssc 的 link
标记,浏览器便发出请求,获取到 CSS 资源文件,与 HTML 类似,我们最终会得到一个称为“CSS 对象模型”(CSSOM) 的树结构。
body { font-size: 16px } p { font-weight: bold } span { color: red } p span { display: none } img { float: right }
以上并非完整的 CSSOM 树,因为浏览器还有默认样式(也称为 User Agent Stlyesheet)。处理 CSS 所需时间在开发者工具 performance 栏中为 Parse Style
与 Recalculate Style
事件。
渲染树构建、布局及绘制
DOM 与 CSSOM 只是独立的数据结构,还需将两者合并最终在浏览器上渲染出来。浏览器做了以下几件事:
display: none
这些过程在 Chrome DevTools 中 Performance 栏中对应的事件是 Layout
、 Paint
与 Composite Layers
。
最终,浏览器完成的步骤有:
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
优化关键渲染路径就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间。
阻塞渲染的 CSS
从浏览器完成页面渲染的过程看:同时具有 DOM 和 CSSOM 才能构建渲染树,所以 HTML 和 CSS 都是阻塞渲染的资源,且 HTML 是必须的,不过没有了样式,页面也是不可用的!
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
JavaScript
现在引入新的变量:JavaScript,接下来看看浏览器遇到 script
标签会如何处理,以下面的 HTML 为例:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path: Script</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script> var span = document.getElementsByTagName('span')[0]; span.textContent = 'interactive'; // change DOM text content span.style.display = 'inline'; // change CSSOM property // create a new element, style it, and append it to the DOM var loadTime = document.createElement('div'); loadTime.textContent = 'You loaded this page on: ' + new Date(); loadTime.style.color = 'blue'; document.body.appendChild(loadTime); </script> </body> </html>
在此之前我们先看看,JavaScript 的能力:
- JavaScript 可以修改 CSSOM
- JavaScript 可以修改 DOM
当浏览器遇到一个 script
标记时,DOM 构建将暂停,直至脚本完成执行,假如在该 script
之前还有外联的样式,JavaScript 的执行得等待 CSSOM 下载与构建完毕,这么做因为 JavaScript 可能会查询或者修改样式。
结论:
- CSS 会阻塞 JavaScript 脚本的执行。
- JavaScript 的执行会注阻塞脚本后面的 DOM 的解析与渲染。
那么 JavaScript 脚本在文档中的位置就尤为重要了。
渲染阻塞
上文关于渲染阻塞已经说得差不多了这里再总结与补充一下:
CSS
- CSS 会阻塞页面的渲染
- CSS 会阻塞 Javascript 执行
- 动态插入的外链 CSS 不会阻塞 DOM 的解析或渲染
- 动态插入的内联 CSS 会阻塞 DOM 的解析或渲染
JavaScript
- 同步的 JavaScript 都会阻塞 DOM 的解析或渲染
- 异步的 JavaScript 不会阻塞DOM 的解析或渲染
-
async
与defer
的 JavaScript 脚本 - 动态插入的外链 JavaScript 脚本
-
首次渲染
那么如何判定页面发生了首次渲染(first paint)了呢,最佳方式是使用 Chrome DevTools 中的 performance 工具查看:例如下图 Frames 栏中绿色色块开始的地方就是首次渲染开始的时间点。顺便说一下蓝线与红线分别代表文档 DOMContentLoaded
与 Load
事件产生的时间点。
DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后, DOMContentLoaded
事件被触发,而无需等待样式表、图像和子框架的完成加载。
注意: DOMContentLoaded
事件必须等待其所属 script 之前的样式表加载解析完成才会触发。
via MDN
DOMContentLoaded
一般表示 DOM 和 CSSOM 均准备就绪的时间点,很多 JavaScript 逻辑都是在 DOMContentLoaded
之后执行的。所以优化关键渲染路径对页响应用户操作是有积极意义的。
Load
对于文档而言表示所页面上有资源都已经加载完毕,用户角度就是浏览器加载小圈停止旋转。
加载阻塞
对于外链的资源,比如外链的样式、脚本、媒体文件、字体等,比起内联资源还需要一个加载过程,那么浏览器是一个个串行去加载的吗?
考古 speculative preload(预先加载)
在早期浏览器 JavaScript 资源是阻塞加载的,即 script 是串行加载的,即下一个资源必须等待 script 加载并执行完成后才能加载。这样会带来很严重的性能问题。
2008 年,IE 提出了 speculative preload(预先加载)策略,即浏览器遇到 script 资源还会继续搜索其他资源并且加载,随后 Firefox 3.5、Safari 4 和 Chrome 2 也采取了类似策略。
资源加载优先级
浏览器才不会那么傻傻的串行加载资源,首先多个线程并行加载资源,而且其会尽早的提前发现并加载资源,比如 Chrome 在 DOM 解析的时候会提前扫描有那些资源并发送请求。那么问题来了,外部资源那么多,浏览器还有同一域下并发请求资源数限制,比如 Chrome 目前是 6
个。那么在有限的加载通道下,比如有谁先谁后的问题,浏览器是如何确定资源加载优先级的?
比如 Chrome 有如下策略:
<head>
以上只是举例,资源加载策略都是与浏览器自己实现,最好从源码与相关文档中学习。且浏览器也在不断优化加载策略,比如 Chrome 要开始对不在是视口内的图片资源执行懒加载了: Blink LazyLoad 。
评估关键渲染路径
- 关键资源 : 可能阻止网页首次渲染的资源。
- 关键路径长度 : 获取所有关键资源所需的往返次数或总时间。
- 关键字节 : 实现网页首次渲染所需的总字节数
理想状态下
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Critical Path: No Style</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
添加样式
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
- 2 项关键资源
- 2 次或更多次往返的最短关键路径长度
- 9 KB 的关键字节
`
添加 JavaScript
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js"></script> </body> </html>
- 3 项关键资源
- 2 次或更多次往返的最短关键路径长度
- 11 KB 的关键字节
异步 JavaScript
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js" async></script> </body> </html>
- 2 项关键资源
- 2 次或更多次往返的最短关键路径长度
- 9 KB 的关键字节
将 CSS 的 Media Query 设置为 print
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet" media="print"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js" async></script> </body> </html>
- 1 项关键资源
- 1 次或更多次往返的最短关键路径长度
- 5 KB 的关键字节
优化关键渲染路径
“优化关键渲染路径”在很大程度上是指了解和优化 HTML、CSS 和 JavaScript 之间的依赖关系谱。
优化 CSS
精简压缩 CSS
无需解释,更小的 CSS 会减少加载字节,加快 CSS 解析以及页面渲染速度。
将 CSS 位于 <head>
内
首先是便于浏览器尽早发现样式资源尽早执行加载。
PS:比如 Chrome 对此有优化,就算样式位于页面底部,被浏览器资源扫描到也会尝试提升其加载优先级。不过经自测,这种优化策略有点迷并不具有规律,另外更换网络环境,Chrome 也会采取不同的加载策略。
所以,还是老老实实把样式放置在头部吧,而且越靠前越好。
Fast 3G 模式下的资源加载瀑布流:
内联阻塞渲染的 CSS
这么做也能减少关键路径中的往返次数。当然这也有缺陷,比如增大了页面体积,以及无法利用缓存 CSS 缓存,我们需寻求一个平衡点,比如只将关键样式内联到文档内。
这也能避免 FOUC(Flash of Unstyled Content)即内容样式短暂失效,也就是我们通常所说的页面闪烁,由于默认样式与网站样式切换带来的变化所致。产生原因比如长时间等待获取样式资源或者 JavaScript 的阻塞等。
FOUC 在 Chrome 中很少见,因为其会等待 CSSOM 构建完毕后才开始页面渲染,但因其渲染模式会产生白屏现象。
使用 Media Query
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet" media="all"> <link href="portrait.css" rel="stylesheet" media="orientation:portrait"> <link href="print.css" rel="stylesheet" media="print">
符合媒体查询的样式才会阻塞页面的渲染,当然所有样式的下载不会被阻塞,只是优先级会调低。
避免使用 CSS import
被 @import
引入的 CSS 需依赖包含其的 CSS 被下载与解析完毕才能被发现,增加了关键路径中往往返次数。
优化 JavaScript
优化精简 JavaScript、按需加载
同理 CSS。
JavaScript 脚本位于底部
非异步的 JavaScript 会阻塞其之后的 DOM 解析与渲染。
async
与 defer
异步的 JavaScript 不会阻塞 DOM 的解析, async
与 defer
使得浏览器对 JavaScript 外链脚本进行异步处理。
defer
规范中要求 defer
循序执行,且执行时机为 DOM 解析完毕之后执行。
async
以上所述就是小编给大家介绍的《关键渲染路径优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。