使用 Google Analytics 衡量关键效果指标

在此 Codelab 中,您将学习如何使用 Google AnalyticsUser Timings API 来衡量网站或应用的实际性能,并对其进行优化以改善用户体验。

WebPagetest.org 等工具非常适合用于开始优化性能,但网站性能的真正考验始终是来自实际用户的真实数据。

如果您运营网站,很可能已经在使用 Google Analytics 来衡量流量以及设备和浏览器使用情况等指标。只需添加少量额外的代码,您就可以将效果指标纳入其中。

学习内容

  • 如何使用 User Timings API 准确有效地衡量性能指标
  • 如何将这些数据发送到 Google Analytics,以便将其纳入到报告中

所需条件

  • 具有开发者控制台的浏览器
  • Web Server for Chrome,或使用您自己选择的网络服务器
  • 示例代码
  • 文本编辑器
  • (可选)Google Analytics 账号

您打算如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价自己在构建网站或应用方面的经验水平?

新手水平 中等水平 熟练水平

您既可以将所有示例代码下载到您的计算机…

下载 Zip 文件

…或从命令行克隆 GitHub 代码库。

git clone https://github.com/googlecodelabs/performance-analytics.git

示例代码分为多个子目录,分别对应于本 Codelab 中的每个编号步骤。您可以使用此功能轻松跳过代码库中的部分内容,或验证您的实现是否正确。

如果您有权访问差异比较程序,则可以使用该程序来查看每个步骤之间的确切变化。

在此 Codelab 中,您将使用一个加载以下资源的 HTML 文件:

  • 网络字体
  • 样式表
  • 图片
  • JavaScript

您将编写新代码来衡量每种素材资源类型的关键绩效指标。

素材资源效果注意事项

如果您曾阅读过有关性能优化的任何内容,可能已经知道,每种素材资源类型都有各自的怪癖,并且会以各种方式影响整体感知性能。

CSS

例如,样式表会阻止渲染 DOM 中位于其后的所有元素,这意味着浏览器必须先请求、下载并解析样式表,然后才能渲染 DOM 中位于其后的任何内容。因此,最好将样式表放在文档的 <head> 中。由于 CSS 具有阻塞特性,因此通常建议仅将关键 CSS 放在 <head> 中,然后在之后异步加载非关键 CSS。

JavaScript

另一方面,JavaScript 不会阻止渲染,但会阻止 DOM 的解析和构建。这是必需的,因为 JavaScript 可以修改 DOM,这意味着浏览器每次看到 <script> 标记(不包括异步脚本)时,都必须先执行代码,然后再继续处理下一个标记。如果 <script> 标记引用了外部 JavaScript 文件,则必须先下载并执行该代码,然后才能继续。

因此,我们通常建议您在 </body> 结束标记之前加载 JavaScript,以便尽快提供大部分 DOM。

网络字体

如果您加载网络字体,还可以选择阻止渲染文档,直到字体可供使用为止。在这种情况下,务必要了解用户实际需要多长时间才能完成此操作。对于您来说,网络字体的加载速度可能很快,但对于访问您网站的大多数人来说,加载速度却很慢。因此,效果衡量和根据真实数据做出决策非常重要。

图片

最后,图片确实能让网站充满活力,但它们往往也需要最长时间才能加载完毕。了解这实际上意味着什么,并能够发现使用模式与网页加载时间之间的任何相关性,对于了解如何进行优化至关重要。

此 Codelab 的第一步是查看在添加任何性能衡量代码之前,演示网页的外观。

如需查看演示,请创建一个新文件夹,并在其中添加一个名为 index.html 的文件。然后,复制以下代码并将其粘贴到 index.html 文件中。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Performance Analytics Demo</title>

  <!-- Start fonts -->
  <link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
  <!-- End fonts -->

  <!-- Start CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
  <style>
    body { font-family: Roboto, sans-serif; margin: 1em; }
    img { float: left; height: auto; width: 33.33%; }
    .gallery { overflow: hidden; }
  </style>
  <!-- End CSS -->

</head>
<body>

  <div class="container">

    <!-- Start images -->
    <div class="gallery">
      <img src="http://lorempixel.com/380/200/animals/1/">
      <img src="http://lorempixel.com/380/200/animals/2/">
      <img src="http://lorempixel.com/380/200/animals/3/">
    </div>
    <!-- End images -->

    <h1>Performance Analytics Demo</h1>
    <p>Real performance data from real users.</p>

  </div>

  <!-- Start JavaScript -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  <!-- End JavaScript -->

</body>
</html>

接下来,打开“Web Server for Chrome”,然后在您刚刚创建的目录中启动本地服务器。确保选中“自动显示 index.html”。

Screen Shot 2016-05-11 at 1.03.43 PM.png

现在,您应该可以在浏览器中前往 http://127.0.0.1:8887/ 并看到演示文件。代码应如下所示:

Screen Shot 2016-05-11 at 10.59.03 AM.png

运行演示网页后,请花点时间查看代码,了解正在加载的所有各种素材资源类型。在接下来的几个步骤中,您将添加代码来衡量这些资源何时加载完毕并可供用户互动。

如前文“素材资源效果注意事项”部分中所述,CSS 会阻止 DOM 元素的渲染以及 DOM 中位于其后的脚本的执行。

您刚刚创建的演示文件包含以下 CSS,其中引用了 Bootstrap 和一些内嵌样式。

<!-- Start CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
  body { font-family: Roboto, sans-serif; margin: 1em; }
  img { float: left; height: auto; width: 33.33%; }
  .gallery { overflow: hidden; }
</style>
<!-- End CSS -->

由于 CSS 会同时阻止 DOM 元素的渲染和脚本的执行,因此可以通过在存储当前时间的 CSS 之后立即添加 <script> 标记来确定 CSS 何时停止阻止。

您可以通过创建一个变量并为其分配 new Date() 来实现此目的,但借助 User Timings API,有一种更简单的方法:performance.mark 方法。

如需标记 CSS 何时完成对渲染和脚本执行的阻塞,请在结束 <!-- End CSS --> 注释之前立即添加以下代码行。

<script>performance.mark('css:unblock');</script>

performance.mark 方法会在这一确切时间点创建一个高分辨率时间戳,并将其与传递给该方法的任何名称相关联。在本例中,您将标记命名为“css:unblock”。

performance.mark 方法与 performance.measure 方法密切相关,后者用于计算两个标记之间的时间差(除了您自己添加的标记之外,您还可以使用浏览器为 Navigation Timing API 中的各个点自动添加的标记)。

以下实用函数用于测量并返回您添加的标记与 Navigation Timing API 创建的 responseEnd 标记之间的时间时长。

function measureDuration(mark, opt_reference) {
  var reference = opt_reference || 'responseEnd';
  var name = reference + ':' + mark;

  // Clears any existing measurements with the same name.
  performance.clearMeasures(name);

  // Creates a new measurement from the reference point to the specified mark.
  // If more than one mark with this name exists, the most recent one is used.
  performance.measure(name, reference, mark);

  // Gets the value of the measurement just created.
  var measure = performance.getEntriesByName(name)[0];

  // Returns the measure duration.
  return measure.duration;
}

如需开始使用此实用函数,请创建一个名为 perf-analytics.js 的新文件(与 index.html 文件位于同一目录中),然后将上述代码复制并粘贴到该文件中。

现在,此函数已定义,您可以调用它并传入“css:unblock”标记名称。为避免干扰任何其他资源加载,您应推迟运行这些衡量,直到窗口的加载事件触发后才运行。

编写好用于调用此代码的函数后,您的 perf-analytics.js 文件应如下所示:

window.onload = function() {
  measureCssUnblockTime();
};


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the CSS stops blocking rendering, then logs that value to the console.
 */
function measureCssUnblockTime() {
  console.log('CSS', 'unblock', measureDuration('css:unblock'));
}


/**
 * Accepts a mark name and an optional reference point in the navigation timing
 * API and returns the time duration between the reference point and the last
 * mark (chronologically).
 * @param {string} mark The mark name.
 * @param {string=} opt_reference An optional reference point from the
 *     navigation timing API. Defaults to 'responseEnd'.
 * @return {number} The time duration
 */
function measureDuration(mark, opt_reference) {
  var reference = opt_reference || 'responseEnd';
  var name = reference + ':' + mark;

  // Clears any existing measurements with the same name.
  performance.clearMeasures(name);

  // Creates a new measurement from the reference point to the specified mark.
  // If more than one mark with this name exists, the most recent one is used.
  performance.measure(name, reference, mark);

  // Gets the value of the measurement just created.
  var measure = performance.getEntriesByName(name)[0];

  // Returns the measure duration.
  return measure.duration;
}

最后,您需要从 index.html 加载 perf-analytics.js 脚本。为此,请将以下脚本标记添加到主文档中。请务必将其添加在最后,以免它干扰其他资源的加载。

<!-- Start performance analytics -->
<script async src="perf-analytics.js"></script>
<!-- End performance analytics -->

完成此步骤后,您的代码应与 Codelab 代码库的 01-css 目录中的代码一致。

如果您在浏览器中加载该网页并打开开发者控制台,应该会看到类似以下输出的内容:

Screen Shot 2016-05-17 at 11.13.02 AM.png

网页字体通常通过外部样式表加载,如初始演示文件所示:

<!-- Start fonts -->
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
<!-- End fonts -->

由于这是一个指向 CSS 文件的 <link> 标记,因此确定字体何时加载完毕并可供使用似乎很简单,只需在 <link> 之后立即在 <script> 标记内添加一个标记,就像在第 1 步中一样。

遗憾的是,情况并非如此简单。

样式表会阻止 JavaScript 的执行,因为样式表的内容用于构建 CSSOM,并且由于加载的 JavaScript 可能需要访问 CSSOM,因此必须延迟执行,直到 CSSOM 完全构建完成。

问题在于,浏览器可以构建 CSSOM,而无需实际下载字体,这意味着如果您在字体样式表 <link> 标记之后立即通过内嵌脚本标记向 DOM 添加标记,则该标记很可能会在字体完全加载之前发生。

在浏览器中提供字体加载事件之前,需要使用 JavaScript 来确定字体何时真正处于活动状态并可供网页使用。幸运的是,通过 JavaScript 加载字体也能提升性能,因为这样不需要向 CSS 文件发出额外的阻塞请求。

大多数 Web 字体(包括 Google 字体、Typekit 字体和 font.com 字体)都可以通过 webfont.js 脚本加载,该脚本由 Google 和 Typekit 共同开发。

如需更新主文档以使用 webfont.js 加载字体(而不是 <link> 标记),请将代码的字体部分替换为以下内容:

<!-- Start fonts -->
<script>
window.WebFontConfig = {
  google: {families: ['Roboto:400,700,400italic']},
  timeout: 10000,
  active: function() {
    performance.mark('fonts:active');
  }
};
</script>
<script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<!-- End fonts -->

关于上述代码,有两点需要注意:

  • 它会在有效回调中创建“fonts:active”标记,以便您日后衡量字体加载所用的时间。
  • 加载 webfonts.js 的 <script> 标记包含 async 属性,因此不会阻止解析或渲染文档的其余部分(<link> 标记则不然)。

虽然上述代码确实会创建“fonts:active”标记,但测量此标记并将其记录到控制台并不像“css:unblock”标记那样简单。原因是字体加载现在是异步进行的,因此如果您尝试在 window.onload 处理程序中测量“fonts:active”标记(就像您之前使用“css:unblock”时一样),则很可能字体尚未加载。

如需解决此问题,您可以创建一个在字体加载后即会解析的 promise。以下函数可为您执行此操作。复制并粘贴到 perf-analytics.js 文件中:

/**
 * Creates a promise that is resolved once the web fonts are fully load or
 * is reject if the fonts fail to load. The resolved callback calculates the
 * time duration between the responseEnd timing event and when the web fonts
 * are downloaded and active. If an error occurs loading the font, this fact
 * is logged to the console.
 */
function measureWebfontPerfAndFailures() {
  new Promise(function(resolve, reject) {
    // The classes `wf-active` or `wf-inactive` are added to the <html>
    // element once the fonts are loaded (or error).
    var loaded = /wf-(in)?active/.exec(document.documentElement.className);
    var success = loaded && !loaded[1]; // No "in" in the capture group.
    // If the fonts are already done loading, resolve immediately.
    // Otherwise resolve/reject in the active/inactive callbacks, respectively.
    if (loaded) {
      success ? resolve() : reject();
    }
    else {
      var originalAciveCallback = WebFontConfig.active;
      WebFontConfig.inactive = reject;
      WebFontConfig.active = function() {
        originalAciveCallback();
        resolve();
      };
      // In case the webfont.js script fails to load, always reject the
      // promise after the timeout amount.
      setTimeout(reject, WebFontConfig.timeout);
    }
  })
  .then(function() {
    console.log('Fonts', 'active', measureDuration('fonts:active'));
  })
  .catch(function() {
    console.error('Error loading web fonts')
  });
}

同时更新 window.onload 处理脚本以调用此新函数

window.onload = function() {
  measureCssUnblockTime();
  measureWebfontPerfAndFailures();
};

完成此步骤后,您的代码应与 Codelab 代码库的 02-fonts 目录中的代码一致。

如果您在浏览器中加载该网页并打开开发者控制台,应该会看到类似以下输出的内容:

Screen Shot 2016-05-17 at 11.13.22 AM.png

了解图片何时可见并不像看起来那么简单。您在之前的步骤中了解到,样式表和同步 <script> 标记会阻塞渲染、解析和脚本执行。您可能不知道的是,这两种方式都不会阻止浏览器的预加载扫描器。

预加载扫描程序是所有现代浏览器都实现的功能,旨在提高性能,即使对于包含大量阻塞资源的非性能导向型网站也是如此。也就是说,虽然某些资源可能会阻止解析、渲染或脚本执行,但不必阻止下载。因此,浏览器会在开始构建 DOM 之前扫描 HTML 文件,并查找可以立即开始下载的资源。

就图片而言,这意味着在将图片添加到 DOM 时,图片很可能已经下载完毕。这也意味着,图片下载时间不一定是一个良好的效果指标。您应关注的性能指标是用户看到图片的时间点。

如果图片在添加到 DOM 之前下载,那么它在 DOM 中时即为可见状态。另一方面,如果图片在添加到 DOM 之前未下载,则当其 onload 处理程序触发时,该图片会变为可见状态。

因此,为了知道图片何时可见,您必须处理这两种情况。

为此,您可以在每个图片的 onload 处理程序中以及 DOM 中最后一个图片之后的内嵌 <script> 标记中添加标记。其理念是,最后出现的标记将表示所有图片都可见的时间。

如需同时为图片加载和渲染时添加标记,请按如下方式更新 index.html 中的图片代码:

<!-- Start images -->
<div class="gallery">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>performance.mark('img:visible')</script>
<!-- End images -->

由于特定标记名称的 performance.measure 方法始终会使用最后一个标记(如果找到多个同名标记),因此 perf-analytics.js 文件中的 measureDuration 实用程序函数可用于此目的,无需进行任何额外修改:

/**
 * Calculates the time duration between the responseEnd timing event and when
 * all images are loaded and visible on the page, then logs that value to the
 * console.
 */
function measureImagesVisibleTime() {
  console.log('Images', 'visible', measureDuration('img:visible'));
}

将上述函数添加到 perf-analytics.js 文件中,然后更新 window.onload 处理程序以调用该函数:

window.onload = function() {
  measureCssBlockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
};

完成此步骤后,您的代码应与 Codelab 代码库的 03-images 目录中的代码一致。

如果您在浏览器中加载该网页并打开开发者控制台,应该会看到类似以下输出的内容:

Screen Shot 2016-05-17 at 11.13.39 AM.png

由于不含 async 属性的 <script> 标记会阻止 DOM 解析,直到下载并执行完毕为止,因此您可以在 DOM 中最后一个同步 <script> 之后的内嵌脚本标记中添加标记,以确定所有脚本完成执行的时间点。

请注意,在这种情况下,使用 onload 处理程序将不起作用,因为浏览器必须在加载脚本后执行脚本,而这需要时间。加载速度快但执行速度慢的脚本与加载速度慢的脚本一样糟糕。

如需跟踪主文档中所有 JavaScript 何时加载和执行,请使用以下代码更新 index.html 中的 JavaScript 部分:

<!-- Start JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>performance.mark('js:execute');</script>
<!-- End JavaScript -->

这会在 jQuery 和 Bootstrap 插件的脚本下载并执行完毕后立即添加一个名为“js:execute”的标记。

如需衡量此过程需要多长时间,请将以下函数添加到 perf-analytics.js

/**
 * Calculates the time duration between the responseEnd timing event and when
 * all synchronous JavaScript files have been downloaded and executed, then
 * logs that value to the console.
 */
function measureJavaSciptExecutionTime() {
  console.log('JavaScript', 'execute', measureDuration('js:execute'));
}

然后从 window.onload 处理程序中调用它:

window.onload = function() {
  measureCssBlockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
  measureJavaSciptExecutionTime();
};

完成此步骤后,您的代码应与 Codelab 代码库的 04-javascript 目录中的代码一致。

如果您在浏览器中加载该网页并打开开发者控制台,应该会看到类似以下输出的内容:

Screen Shot 2016-05-17 at 11.14.03 AM.png

并非所有浏览器都支持 JavaScript Promise 或 User Timing API,如果您在不支持其中一项功能的浏览器中运行目前为止编写的代码,就会收到错误。

为了解决这个问题,您可以使用功能检测。在字体部分之前立即添加以下代码段。这行 JavaScript 代码用于检测对 performance.mark 方法的支持,因此必须在首次使用该方法之前添加到网页中:

<!-- Start feature detects -->
<script>window.__perf = window.performance && performance.mark;</script>
<!-- End feature detects -->

接下来,在 index.html 中调用 performance.mark 的任何位置,为其添加功能检测前缀。以下示例更新了图片块中的代码,但您必须确保也更新其他部分。

<!-- Start images -->
<div class="gallery">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>__perf && performance.mark('img:visible')</script>
<!-- End images -->

现在,__perf 变量已在 window 上设置,您还可以在 perf-analytics.js 中使用它,以确保您不会调用当前浏览器不支持的方法。

必须更新 measureDuration 函数以添加以下条件:

function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    // ...
  }
}

最后,由于并非所有浏览器都支持 JavaScript Promise,因此 measureWebfontPerfAndFailures 函数也必须封装在条件中:

function measureWebfontPerfAndFailures() {
  if (window.Promise) {
    // ...
  }
}

现在,您应该能够在任何浏览器中顺利运行代码了。

完成此步骤后,您的代码应与 Codelab 代码库的 05-feature-detects 目录中的代码一致。

本 Codelab 的最后一步是将记录到控制台的数据发送到 Google Analytics。在向 Google Analytics 发送数据之前,您必须先将 analytics.js 库默认跟踪代码段添加到网页中。

将以下代码添加到 index.html 中的主 JavaScript 代码块之后,但在加载 perf-analytics.js 脚本之前:

<!-- Start analytics tracking snippet -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics_debug.js"></script>
<!-- End analytics tracking snippet -->

如果您之前曾向网站添加过 Google Analytics,您就会知道必须将“UA-XXXXX-Y”占位符替换为您在 Google Analytics 中创建新媒体资源时收到的跟踪 ID。

analytics.js 跟踪代码段主要执行以下四项操作:

虽然仅从网页浏览收集的数据很有用,但它并不能完整体现整个转化历程。为了更好地了解用户在您的网站或应用中的体验,您必须向 Google Analytics 发送额外的互动数据。

Google Analytics 支持多种类型的互动数据:网页浏览事件社交互动例外情况,以及(最后但同样重要)用户计时。如需将用户计时数据发送到 Google Analytics,您可以使用以下命令签名:

ga('send', 'timing', timingCategory, timingVar, timingValue);

其中,timingCategory 是一个字符串,可用于将计时命中整理到逻辑组中;timingVar 是您要衡量的变量;timingValue 是实际的时间时长(以毫秒为单位)。

如需了解此方法在实践中的效果,可以按如下所示更新 measureCssUnblockTime 函数中的 console.log 语句:

ga('send', 'timing', 'CSS', 'unblock', measureDuration('css:unblock'));

虽然上述代码在某些情况下可以正常运行,但需要注意以下两个重要陷阱:

  • 上一步更新了 measureDuration 函数,使其仅在浏览器支持 User Timings API 时运行,这意味着有时它会返回 undefined。由于没有理由将未定义的数据发送到 Google Analytics(在某些情况下,这甚至可能会扰乱您的报告),因此您只有在 measureDuration 返回值时才应发送此计时命中。
  • measureDuration 返回值时,该值为 DOMHighResTimeStamp,精确度高于毫秒级。由于 Google Analytics 中的 timingValue 必须是整数,因此您必须对 measureDuration 返回的值进行四舍五入。

为了解决这些问题,请更新 measureDuration 函数中的 return 语句,以对返回值进行四舍五入:

function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    // ...
    return Math.round(measure.duration);
  }
}

并更新了计时命令,使其仅在相关指标有值时运行。例如,应将 measureCssUnblockTime 函数更新为如下所示:

function measureCssUnblockTime() {
  var cssUnblockTime = measureDuration('css:unblock');
  if (cssUnblockTime) {
    ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
  }
}

您需要对所有其他衡量函数进行类似的更新。完成后,最终的 perf-analytics.js 文件应如下所示:

window.onload = function() {
  measureCssUnblockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
  measureJavaSciptExecutionTime();
};


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the CSS stops blocking rendering, then sends this measurement to Google
 * Analytics via a timing hit.
 */
function measureCssUnblockTime() {
  var cssUnblockTime = measureDuration('css:unblock');
  if (cssUnblockTime) {
    ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the web fonts are downloaded and active, then sends this measurement to
 * Google Analytics via a timing hit. If an error occurs loading the font, an
 * error event is sent to Google Analytics.
 */
function measureWebfontPerfAndFailures() {
  if (window.Promise) {
    new Promise(function(resolve, reject) {
      var loaded = /wf-(in)?active/.exec(document.documentElement.className);
      var success = loaded && !loaded[1]; // No "in" in the capture group.
      if (loaded) {
        success ? resolve() : reject();
      }
      else {
        var originalAciveCallback = WebFontConfig.active;
        WebFontConfig.inactive = reject;
        WebFontConfig.active = function() {
          originalAciveCallback();
          resolve();
        };
        // In case the webfont.js script failed to load.
        setTimeout(reject, WebFontConfig.timeout);
      }
    })
    .then(function() {
      var fontsActiveTime = measureDuration('fonts:active');
      if (fontsActiveTime) {
        ga('send', 'timing', 'Fonts', 'active', fontsActiveTime);
      }
    })
    .catch(function() {
      ga('send', 'event', 'Fonts', 'error');
    });
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * all images are loaded and visible on the page, then sends this measurement
 * to Google Analytics via a timing hit.
 */
function measureImagesVisibleTime() {
  var imgVisibleTime = measureDuration('img:visible');
  if (imgVisibleTime) {
    ga('send', 'timing', 'Images', 'visible', imgVisibleTime);
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * all synchronous JavaScript files are downloaded and executed, then sends
 * this measurement to Google Analytics via a timing hit.
 */
function measureJavaSciptExecutionTime() {
  var jsExecuteTime = measureDuration('js:execute');
  if (jsExecuteTime) {
    ga('send', 'timing', 'JavaScript', 'execute', jsExecuteTime);
  }
}


/**
 * Accepts a mark name and an optional reference point in the navigation timing
 * API and returns the time duration between the reference point and the last
 * mark (chronologically). The return value is rounded to the nearest whole
 * number to be compatible with Google Analytics.
 * @param {string} mark The mark name.
 * @param {string=} opt_reference An optional reference point from the
 *     navigation timing API. Defaults to 'responseEnd'.
 * @return {?number} The time duration as an integer or undefined if no
 *     matching marks can be found.
 */
function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    var reference = opt_reference || 'responseEnd';
    var name = reference + ':' + mark;

    // Clears any existing measurements with the same name.
    performance.clearMeasures(name);

    // Creates a new measurement from the reference point to the specified mark.
    // If more than one mark with this name exists, the most recent one is used.
    performance.measure(name, reference, mark);

    // Gets the value of the measurement just created.
    var measure = performance.getEntriesByName(name)[0];

    // Returns the measure duration.
    return Math.round(measure.duration);
  }
}

最终的 index.html 文件应如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Performance Analytics Demo</title>

  <!-- Start navigation timing feature detect -->
  <script>window.__perf = window.performance && performance.mark;</script>
  <!-- End navigation timing feature detect -->

  <!-- Start fonts -->
  <script>
  window.WebFontConfig = {
    google: {families: ['Roboto:400,700,400italic']},
    timeout: 10000,
    active: function() {
      __perf && performance.mark('fonts:active');
    }
  };
  </script>
  <script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
  <!-- End fonts -->

  <!-- Start CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
  <style>
    body { font-family: Roboto, sans-serif; margin: 1em; }
    img { float: left; height: auto; width: 33.33%; }
    .gallery { overflow: hidden; }
  </style>
  <script>__perf && performance.mark('css:unblock');</script>
  <!-- End CSS -->

</head>
<body>

  <div class="container">

    <!-- Start images -->
    <div class="gallery">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
    </div>
    <script>__perf && performance.mark('img:visible')</script>
    <!-- End images -->

    <h1>Performance Analytics Demo</h1>
    <p>Real performance data from real users.</p>

  </div>

  <!-- Start JavaScript -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  <script>__perf && performance.mark('js:execute');</script>
  <!-- End JavaScript -->

  <!-- Start analytics tracking snippet -->
  <script>
  window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
  ga('create', 'UA-XXXXX-Y', 'auto');
  ga('send', 'pageview');
  </script>
  <script async src="https://www.google-analytics.com/analytics.js"></script>
  <!-- End analytics tracking snippet -->

  <!-- Start performance analytics -->
  <script async src="perf-analytics.js"></script>
  <!-- End performance analytics -->

</body>
</html>

如果您加载此网页并查看网络面板中的请求,则会看到如下内容:

Screen Shot 2016-05-10 at 6.57.23 PM.png

这很有用,但以网址编码请求的形式查看此数据可能会很麻烦。而且,如果出于某种原因,您没有看到这些请求,就很难追踪到失败发生的位置。

在本地开发时,更好的方法是使用 analytics.js 的调试版本,该版本会在每次运行 analytics.js 命令时将有用的调试信息记录到控制台。如果您

index.html 中的 analytics.js 网址更新为 analytics_debug.js,然后打开浏览器控制台,您会看到打印出的语句,如下所示:

Screen Shot 2016-05-10 at 6.54.13 PM.png

现在,您已经了解如何为此演示网页实现性能衡量,接下来可以尝试将其添加到您自己的网站中,将真实用户数据发送给 Google Analytics。

根据您收集的数据生成报告

在收集了几天效果数据后,您就可以根据这些数据生成报告,从而深入了解您的网站及其资源在实际用户面前的加载速度。

如需在 Google Analytics 中查看“用户计时”报告,请点击顶部的“报告”标签页,然后从侧边栏导航中选择“行为 > 网站速度 > 用户计时”(或按照帮助中心中的说明查看用户计时报告)。

在 Google Analytics 中加载“用户计时”报告时,您应该能够看到与您发送的数据对应的计时类别。点击其中任意一项,即可查看时间数据的详细可视化图表。下图显示了过去 24 小时内使用 Google Fonts 的真实网站上的字体加载时间示例。

Screen Shot 2016-05-10 at 7.16.07 PM.png

恭喜!您已成功完成此 Codelab。如果您想深入了解,下一部分将提供一些建议,帮助您在此代码的基础上构建更深入的数据分析。

此 Codelab 中介绍的性能指标对于衡量网站在真实用户面前的加载速度至关重要,但它们只是一个开始。如果您想深入了解效果分析,一个简单的下一步就是跟踪更多指标。

在此代码实验中,您跟踪了与资源何时可供用户使用相关的指标。如果需要,您还可以进一步细分这些类别中的大多数。例如,您可以测量 JavaScript 开始加载的时间、完成加载的时间、开始执行的时间,以及最终完成执行的时间,而不仅仅是测量 JavaScript 完成执行的时间。这些指标中的每一个都可能揭示仅靠其中一个指标无法发现的问题。

除了获得更精细的数据之外,您还应从更全面的角度考虑总体效果分析策略。目标是什么?什么是成功?

在进行任何类型的分析时,您通常都需要先提出某种问题,然后再想办法使用数据来回答该问题。

例如,请看以下问题列表,以及如何使用在本代码实验中获得的知识来通过分析回答这些问题:

  • 您跟踪的指标值是否随时间推移而减少或增加?
  • 通过 Service Worker 或本地存储使用离线缓存会对网站的整体性能产生什么影响?
  • 资源是否以最佳方式加载?即,资源下载完毕后,是否需要等待很长时间才能使用?
  • 效果与您跟踪的其他指标(例如注册率、网站停留时间、购买次数等)之间是否存在任何相关性?

最后,如果您想详细了解网站性能或 Google Analytics,可以参阅以下优质资源: