使用 Google Analytics 評估重要成效指標

在本程式碼研究室中,您將瞭解如何使用 Google AnalyticsUser Timings API 評估網站或應用程式的實際效能,並進行最佳化,提升使用者體驗。

WebPagetest.org 等工具是最佳化效能的絕佳起點,但網站效能的真正測試,一律是來自實際使用者的真實資料。

如果您經營網站,很可能已使用 Google Analytics 評估流量,以及裝置和瀏覽器使用情況等。只要加入少許額外程式碼,即可在組合中加入成效指標。

課程內容

  • 如何使用 User Timings API 準確有效地評估效能指標
  • 如何將資料傳送至 Google Analytics,以便納入報表

軟硬體需求

您會如何使用本教學課程?

僅閱讀 閱讀並完成練習

您對建構網站或應用程式的體驗滿意嗎?

新手 中級 熟練

您可以將所有範例程式碼下載至電腦,

下載 ZIP 檔

...或從指令列複製 GitHub 存放區。

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

範例程式碼會分成多個子目錄,分別對應本程式碼研究室中編號的步驟。您可以使用這個功能輕鬆跳過程式碼研究室中的部分內容,或確認導入作業是否正確。

如果您有權存取差異比較程式,可以使用該程式查看每個步驟的確切變更內容。

在本程式碼研究室中,您將使用載入下列資產的單一 HTML 檔案:

  • 網頁字型
  • 樣式表
  • 圖片
  • JavaScript

您將編寫新的程式碼,用來評估每種素材資源類型的主要成效指標。

素材資源成效注意事項

如果您曾閱讀任何有關效能最佳化的內容,可能已經知道這些資產類型各有不同特性,且會以各種方式影響整體感知效能。

CSS

舉例來說,樣式表會封鎖 DOM 中所有後續元素的算繪作業,也就是說,瀏覽器必須先要求、下載及剖析樣式表,才能算繪 DOM 中後續的任何內容。因此,建議您將樣式表放在文件的 <head> 中。此外,由於 CSS 會造成阻斷,因此通常建議只將重要 CSS 放在 <head> 中,然後以非同步方式載入非重要 CSS。

JavaScript

另一方面,JavaScript 不會封鎖轉譯,但會封鎖 DOM 的剖析和建構作業。這是必要的,因為 JavaScript 可以修改 DOM,也就是說,瀏覽器每次看到 <script> 標記 (不含非同步指令碼) 時,都必須先執行程式碼,才能繼續處理下一個標記。如果 <script> 標記參照外部 JavaScript 檔案,則必須先下載並執行程式碼,才能繼續。

因此,我們通常建議您在結尾 </body> 標記之前載入 JavaScript,以便盡快提供大部分的 DOM。

網路字型

載入網路字型時,您也可以選擇封鎖文件算繪作業,直到字型可供使用為止。在這種情況下,瞭解使用者實際完成這項操作所需的時間至關重要。網頁字型可能在你的裝置上快速載入,但對大多數網站訪客來說,載入速度卻很慢。因此,根據實際資料評估成效並做出決策非常重要。

圖片

最後,圖片能讓網站活起來,但往往也需要最長的時間載入。瞭解這項指標的實際意義,並找出使用模式與網頁載入時間之間的任何關聯性,對於瞭解如何最佳化至關重要。

在本程式碼研究室中,您要做的第一件事是查看示範網頁的外觀,然後再新增任何效能評估程式碼。

如要查看示範內容,請建立新資料夾,並在其中新增名為 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 -->

完成這個步驟後,您的程式碼應與程式碼研究室存放區的 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 檔案提出額外的封鎖要求。

大多數網路字型 (包括 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();
};

完成這個步驟後,您的程式碼應與程式碼研究室存放區的 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();
};

完成這個步驟後,您的程式碼應與程式碼研究室存放區的 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();
};

完成這個步驟後,您的程式碼應與程式碼研究室存放區 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) {
    // ...
  }
}

現在您應該可以在任何瀏覽器中順利執行程式碼。

完成這個步驟後,您的程式碼應與程式碼研究室存放區的 05-feature-detects 目錄內容相符。

本程式碼研究室的最後一個步驟,是將記錄到控制台的資料傳送至 Google Analytics。您必須先在網頁中加入 analytics.js 程式庫預設追蹤程式碼片段,才能將資料傳送至 Google Analytics。

在主要 JavaScript 區塊之後,但在載入 perf-analytics.js 指令碼之前,將下列程式碼新增至 index.html

<!-- 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 字型的實際網站字型載入時間範例。

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

恭喜!您已成功完成本程式碼研究室。如要深入瞭解,請參閱下一節,瞭解如何以這段程式碼為基礎,進一步取得洞察資料。

本程式碼研究室涵蓋的效能指標,對於評估網站為實際使用者載入的情形至關重要,但這只是開始。如要深入瞭解成效分析,最簡單的方法就是追蹤更多指標。

在本程式碼研究室中,您追蹤了與資源何時可供使用者存取相關的指標。如有需要,您還可以進一步細分這些類別。舉例來說,您可以評估 JavaScript 開始載入、完成載入、開始執行,以及最終完成執行的時間,而不只是評估 JavaScript 完成執行的時間。這些指標各自可能揭露單一指標無法顯示的問題。

除了更精細地分析資料,您也應從整體角度思考一般成效分析策略。目標是什麼?什麼是成功?

進行任何類型的分析時,通常會先提出問題,然後找出如何使用資料回答該問題。

舉例來說,請參考下列問題清單,並思考如何運用在本程式碼研究室學到的知識,使用 Analytics 回答這些問題:

  • 您追蹤的指標值是否隨著時間減少或增加?
  • 透過 Service Worker 或本機儲存空間使用離線快取,會對網站整體效能造成什麼影響?
  • 資源是否以最佳方式載入?也就是說,資源下載完成後,是否要過很久才能使用?
  • 您追蹤的成效和其他指標 (例如註冊率、網站停留時間、購買次數等) 之間是否有任何關聯?

最後,如要進一步瞭解網頁效能或 Google Analytics,請參閱下列實用資源: