关键渲染路径优化

栏目: CSS · 发布时间: 8年前

内容简介:天下武功唯快不破,页面加载速度与用户体验息息相关,也关乎企业收益:最知名的当属 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)。

关键渲染路径优化

  1. 转换 : 浏览器读取 HTML 原始字节,根据指定编码转化为字符
  2. 令牌化 :将字符转化为 token 以及字符串
  3. 词法分析 :将 token 转化为定义其属性与规则的 node
  4. 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 。

最终,浏览器完成的步骤有:

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

优化关键渲染路径就是指最大限度缩短执行上述第 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 的能力:

  1. JavaScript 可以修改 CSSOM
  2. JavaScript 可以修改 DOM

关键渲染路径优化

当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行,假如在该 script 之前还有外联的样式,JavaScript 的执行得等待 CSSOM 下载与构建完毕,这么做因为 JavaScript 可能会查询或者修改样式。

关键渲染路径优化

结论:

  1. CSS 会阻塞 JavaScript 脚本的执行。
  2. 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

关键渲染路径优化


以上所述就是小编给大家介绍的《关键渲染路径优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

C专家编程

C专家编程

Peter Van Der Linden / 徐波 / 人民邮电出版社 / 2008-2 / 45.00元

《C专家编程》展示了最优秀的C程序员所使用的编码技巧,并专门开辟了一章对C++的基础知识进行了介绍。 书中C的历史、语言特性、声明、数组、指针、链接、运行时、内存以及如何进一步学习C++等问题进行了细致的讲解和深入的分析。全书撷取几十个实例进行讲解,对C程序员具有非常高的实用价值。 本书可以帮助有一定经验的C程序员成为C编程方面的专家,对于具备相当的C语言基础的程序员,本书可以帮助他们......一起来看看 《C专家编程》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具