Статьи

Повышение производительности сайта с помощью API Navigation Timing

Автор Аурелио де Роза

Сеть развивается в сумасшедшем темпе. Каждый день выпускаются новые фреймворки, инструменты и библиотеки с амбициями, если не потенциалом, стать следующим jQuery. Как разработчик, я чувствую, что иногда действительно трудно быть в курсе всего нового программного обеспечения, методов и практик, представленных первоклассными разработчиками и дизайнерами. Тем не менее, эта тенденция, кажется, движет веб-платформу вперед по таким вопросам, как производительность, безопасность и доступность.

Частично благодаря этим усилиям такие организации, как W3C, стремятся к созданию, стандартизации и принятию новых API, ориентированных на эти области, в частности на производительность. За последние несколько лет браузерами было предложено и реализовано множество новых API-интерфейсов JavaScript, которые помогают разработчикам измерять и повышать производительность веб-приложений. Например, вы можете использовать User Timing API для точного измерения производительности фрагмента кода, имея доступ к высокоточным временным меткам, и Timing Resource API для сбора полной информации о синхронизации, связанной с ресурсами в документе.

However, when it comes to performance, the loading time of a page is an especially important aspect of the overall user experience. If a web page loads too slowly, users quickly become frustrated and are more likely to abandon the page. Hence, your business loses potential customers and revenue. The loading time of a page can be influenced by many factors such as the network speed, the server load, the user latency, and the performance of the code of the page.

In this article I’ll introduce you to a new JavaScript API, called the Navigation Timing API, that will help you in measuring the performance of your web pages.

What’s the Navigation Timing API?

The Navigation Timing API provides Web applications with timing-related information. This API exposes several properties that offer information about the time at which certain events happen, like the time immediately before the DNS lookup for the URL occurs. It does not, however, provide any methods or events to listen to.

This API is a W3C Recommendation which means that its specifications are set in stone and won’t change in the future unless a new version is released. This means that this is an API you can start using today.

Using the Resource Timing API allows us to retrieve and analyze a detailed profile of all the network timing data for a given page. Once you retrieve the data using this API, you can send them to your server using an Ajax call. Doing so, you can understand if the page has issues that need to be addressed.

Developers have have become accustomed to measuring the loading time of a page using code similar to what is shown:

<!doctype html>
<html>
   <head>
      <script>
         var start = new Date().getTime();
         window.onload = function() {
            var end = new Date().getTime();
            console.log('Loading time (in milliseconds): ' + (end - start));
         }
      </script>
   </head>
   <body>
   </body>
</html>

Although very simple, the code above has several issues. The first one is that JavaScript time is notoriously inaccurate and is skewed by adjustments to the system clock. In addition, the Date object can only measure the execution time once the code is running in the browser. It can’t provide any data regarding the page load process involving the server, network, and so on.

Using the Navigation Timing API we can obtain a more detaied measure of the user’s perceived loading time as shown below:

<!doctype html>
<html>
   <head>
      <script>
         window.addEventListener('load', function() {
            var now = new Date().getTime();
            console.log('Perceived loading time (in milliseconds): ' + (now - performance.timing.navigationStart));
         });
      </script>
   </head>
   <body>
   </body>
</html>

Now that we know what this API is, let’s delve into its properties.

Properties

The Navigation Timing API is exposed through the timing property of the window.performance object. The events measured are offered as properties of timing. Below you find a list of them in the order they happen:

  • navigationStart: The time immediately after the browser finishes prompting to unload the previous document. If there is no previous document, then navigationStart is equal to fetchStart. This is the beginning of the page load time as perceived by the user.
  • unloadEventStart: The time immediately before the previous document’s unload event is fired. If there is no previous document, or if the previous document is from a different origin, then this value is zero.
  • unloadEventEnd: The time immediately after the previous document’s unload event is fired. If there is no previous document, or if the previous document is from a different origin, then this value is zero. If there are any redirects that point to a different origin, then unloadEventStart and unloadEventEnd are both zero.
  • redirectStart: The start time of a URL fetch that initiates a redirect.
  • redirectEnd: If any redirects exist, this is the time after the last byte of the last redirect response is received.
  • fetchStart: The time immediately before the browser begins searching for the URL. The search process involves checking application caches or requesting the file from the server if it is not cached.
  • domainLookupStart: The time immediately before the DNS lookup for the URL occurs. If no DNS lookup is required, then the value is the same as fetchStart.
  • domainLookupEnd: The time immediately after the DNS lookup occurs. If a DNS lookup is not required, then the value is the same as fetchStart.
  • connectStart: The time immediately before the browser connects to the server. This value is equal todomainLookupEnd if the URL is a cached or local resource.
  • connectEnd: The time the connection to the server is established. If the URL is a cached or local resource, then this value is the same as domainLookupEnd.
  • secureConnectionStart: If the HTTPS protocol is used, this is the time immediately before the secure handshake begins; otherwise this value is undefined.
  • requestStart: The time before the browser sends the request for the URL.
  • responseStart: The time immediately after the browser receives the first byte of the response.
  • responseEnd: The time immediately after the browser receives the last byte of the response.
  • domLoading: The time immediately before the document.readyState value is set to loading.
  • domInteractive: The time immediately before the document.readyState value is set to interactive.
  • domContentLoadedEventStart: The time immediately before the DOMContentLoaded event is fired.
  • domContentLoadedEventEnd: The time immediately after the DOMContentLoaded event is fired.
  • domComplete: The time immediately before the document.readyState value is set to complete.
  • loadEventStart: The time immediately before the window’s load event is fired.
  • loadEventEnd: The time immediately after the window’s load event is fired.

By using these values, you can gauge certain components of the page load time. For example, you can measure the time spent to perform a DNS lookup by subtracting domainLookupStart from domainLookupEnd.

The following image taken from the official specifications offers a graphical representation of the properties I just described. Those that are underlined may not be available when fetching resources from different origins:

Атрибуты синхронизации, определенные интерфейсом PerformanceTiming и интерфейсом PerformanceNavigation

The timing attributes defined by the PerformanceTiming interface and the PerformanceNavigation interface

In addition to the previous set of properties, the Navigation Timing API also defines another object to determine how a user landed on a particular page. This object is called navigation and belongs to the window.performanceobject as well. It provides the following properties:

  • type: The method by which the user navigated to the current page. This property can assume one of the following values: 012255. A value of zero means the user landed by typing a URL, clicking a link, submitting a form, or through a script operation. A value of one means the user reloaded the page. A value of two means the user landed on the page via history (back or forward buttons). A value of 255 is an umbrella value for any other reason.
  • redirectCount: The number of redirects taken to the current page. If no redirects occurred, or if any of the redirects were from a different origin, this value is zero.

Now that Ive bored you enough with extremely long lists of properties, let’s take a look at which browsers support this API.

Browsers Support

Browser support for this API is very good on both desktop and mobile. On desktop, the Navigation Timing API has been implemented in Chrome 6+, Firefox 7+, Internet Explorer 9+, and Opera 15+. So, the only desktop browser that does not currently support this API is Safari. On mobile, the situation is very similar, with the addition of iOS Safari 8+, and Android Browser 4+, Blackberry Browser, and UC Browser for Android. Therefore, Opera Mini is the only mobile browser that does not currently support this API.

Despite the fact that this API is widely supported, it’s still advisable to adhere to the principles of the progressive enhancement and always test for support. To do that, you can employ the code below:

if ( !('performance' in window) ||
     !('timing' in window.performance) ||
     !('navigation' in window.performance)
) {
  // API not supported
} else {
   // API supported
}

Demo

Let’s build a simple demo that allows us to see this API in action and the information it provides. First, we check whether the Navigation Timing API is supported or not. If the API isn’t supported, we display the message “API not supported.” Otherwise the code displays all the timing details provided by the API as soon as the load event of thewindow object is fired.

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
      <meta name="author" content="Aurelio De Rosa">
      <title>Navigation Timing API Demo by Aurelio De Rosa</title>

      <style>
         *
         {
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
         }

         body
         {
            max-width: 500px;
            margin: 2em auto;
            padding: 0 0.5em;
            font-size: 20px;
         }

         h1
         {
            text-align: center;
         }

         .hidden
         {
            display: none;
         }

         .value
         {
            font-weight: bold;
         }

         .author
         {
            display: block;
            margin-top: 1em;
         }
      </style>
   </head>
   <body>
      <h1>Navigation Timing API</h1>

      <span id="nt-unsupported" class="hidden">API not supported</span>

      <h2>Timing info</h2>
      <ul id="timing-list">
      </ul>

      <h2>Navigation info</h2>
      <ul id="navigation-list">
      </ul>

      <small class="author">
         Demo created by <a href="http://www.audero.it">Aurelio De Rosa</a>
         (<a href="https://twitter.com/AurelioDeRosa">@AurelioDeRosa</a>).<br />
         This demo is part of the <a href="https://github.com/AurelioDeRosa/HTML5-API-demos">HTML5 API demos repository</a>.
      </small>

      <script>
         if ( !('performance' in window)            ||
              !('timing' in window.performance)     ||
              !('navigation' in window.performance)
         ) {
            document.getElementById('nt-unsupported').className = '';
         } else {
            window.addEventListener('load', function() {
               var list = '';
               var timings = window.performance.timing;
               for(var timing in timings) {
                  list += '<li>' + timing + ': <span class="value">' + timings[timing] + '</span></li>';
               }
               document.getElementById('timing-list').innerHTML = list;

               list = '';
               list += '<li>redirectCount: <span class="value">' + window.performance.navigation['redirectCount'] + '</span></li>';
               list += '<li>type: <span class="value">' + window.performance.navigation['type'] + '</span></li>';
               document.getElementById('navigation-list').innerHTML = list;
            });
         }
      </script>
   </body>
</html>

You can view the code live here.

Conclusion

Ensuring your site loads quickly is a major concern for developers today. The Navigation Timing API is one of the several APIs you can employ but, as you’ve learned in this tutorial, it can provide a high level of detail while being very easy to use. Finally, the support for this API is very good among desktop and mobile browsers, which means that you can start using it today.