Introduction to Page Visibility API

Mobile is cool. Mobile apps are even cooler. Unfortunately, in most cases, mobile connections suck because they are slow or you don’t haven unlimited bandwidth. It would be great to have rich web applications that don’t waste users’ resources, especially when they aren’t looking at that page. This article will show you how to partially solve this and other problems using the Page Visibility API.

In the last few years, several new great APIs have been introduced to help us in our everyday work, such as Geolocation API, Navigation Timing API and Full-screen API. Page Visibility API defines a means for site developers to programmatically determine the current visibility state of the page in order to develop powerful and CPU efficient web applications. From July 26th 2012, it’s a W3C Candidate Recommendation so it’s considered stable.

The first thing you might wonder is how they improve performance and save bandwidth. Imagine a situation where you have a great AJAX-based web application that send data back and forth every five seconds. If the user sends the browser tab to background when your application is running, it’ll still send data every five seconds, and also if the user takes the tab in foreground after 10 minutes. Wouldn’t it be great if the application slowed down the updates or stopped them until the user looked at the page again? Here’s where the resources optimization comes in, and where the Page Visibility API plays the key role.

How Page Visibility APIs are made

These APIs are quite simple, in fact they have a single event called visibilitychange and two read-only properties belonging to document, hidden and visibilityState. hidden is a boolean value which is true if the page is not visible, even the smallest part, and this typically happens when the tab is in background or the browser is minimized. It’s important to note that this rule has some exceptions for accessibility tools that act in full-screen mode. You can find out more on this by reading the hidden specifications.

visibilityState is an enumeration that specifies the current state of the document and consists of the following values:

  • hidden: The document is not visible at all
  • visible: The document or a part of it is visible
  • prerender: The document is loaded off-screen and isn’t visible
  • unloaded: The document is going to be unloaded

Please note that the last two values, prerender and unloaded, are optional. Besides, like the hidden attribute, the hidden value has some exceptions regarding assistive technologies.

Compatibility

Currently, there aren’t many browsers that support these APIs and those that do still use vendor prefixes. This leads to support problems because you have to manage all the prefixes to have a working code. Currently the desktop browsers that support the Page Visibility API are Chrome 13+, Internet Explorer 10, Firefox 10+, Opera beta 12.10. The mobile browsers that support the API are Chrome on Android 4.0+ and Opera Mobile 12.1+ on both Android and Symbian (source MobileHTML5.org – tested by myself on Android 4.0).

A mildly annoying point is that due to the camelCase convention, if the properties are vendor prefixed, the actual property name has the first letter capitalized, while it’s lowercase if it isn’t prefixed. For the sake of clarity, let’s take the hidden property. As you can see, it starts with a lowercase letter but if it’s prefixed it starts with an uppercase “h”, so to test the support you can’t write code that resembles the following:

var browserPrefixes = ["", "webkit","moz","ms","o"];
for(var i = 0; i < browserPrefixes.length; i++) {
  if (document[browserPrefixes[i] + "hidden"] != undefined)
    // here goes the code
}

And you have to split the cases, like the following, or use some trick against strings.

// Test for unprefixed version
if (document.hidden !== undefined)
  // here goes the code
else {
  // Test for prefixed version
  var browserPrefixes = ["webkit", "moz", "ms", "o"];
  for(var i = 0; i < browserPrefixes.length; i++) {
    if (document[browserPrefixes[i] + "Hidden"] != undefined) {
      // here goes the code
    }
  }
}

As always, just like other APIs, a bunch of polyfills have been released to use those APIs in browsers that don’t support them. Some of these polyfills are visibly.js and isVis.js.

Let’s create a working example

In this section, I’ll guide you through creating a simple demo page that uses the Page Visibility API. The page will firstly test for browser support and then count the times the user actually sees the page and log its states. There are just three key functions in our demo. The first tests if the browser uses a vendor prefixed version or not, and that will be created on top of the last code shown. It will return an empty string if the browser uses the unprefixed version, the vendor prefix if it uses a prefixed version, or null in the case that the browser doesn’t support the API.

function getPrefix() {
  var prefix = null;
  if (document.hidden !== undefined)
    prefix = "";
  else {
    var browserPrefixes = ["webkit","moz","ms","o"];
    // Test all vendor prefixes
    for(var i = 0; i < browserPrefixes.length; i++) {
      if (document[browserPrefixes[i] + "Hidden"] != undefined) {
        prefix = browserPrefixes[i];
        break;
      }
    }
  }
  return prefix;
}

The second function logs the state and increments the view count when the page is displayed.

function countView() {
  // The page is in foreground and visible
  if (document.hidden === false || document[prefix + "Hidden"] === false)
    views++;

  document.getElementById("log").innerHTML += "Your view count is: " + views + ". " + "Your page current state is: " + document[(prefix === "" ? "v" : prefix + "V") + "isibilityState"] + "
";
}

The third and last function tests whether the browser supports the API and if the test is true, it attaches a handler to the visibilitychange event, or otherwise it notifies the user. Please note that this function, too, is needed to manage the vendor prefixes.

function testPageVisibilityApi() {
  if (prefix === null)
    document.getElementById("log").innerHTML = "Your browser does not support Page Visibility API";
  else {
    document.addEventListener(prefix + "visibilitychange", countView);
    countView();
  }
}

Putting it all together

Given the functions shown in the previous section, the final and completely working code is the following.

<!DOCTYPE html>
<html>
  <head>
    <title>Page Visibility API Test Page by Aurelio De Rosa</title>
    <script>
      function getPrefix() {
        var prefix = null;
        if (document.hidden !== undefined)
          prefix = "";
        else {
          var browserPrefixes = ["webkit","moz","ms","o"];
          // Test all vendor prefixes
          for(var i = 0; i < browserPrefixes.length; i++) {
            if (document[browserPrefixes[i] + "Hidden"] != undefined) {
              prefix = browserPrefixes[i];
              break;
            }
          }
        }
        return prefix;
      }

      function countView() {
        // The page is in foreground and visible
        if (document.hidden === false || document[prefix + "Hidden"] === false)
          views++;

        document.getElementById("log").innerHTML += "Your view count is: <b>" + views +
          "</b>. " + "Your page current state is: <b>" +
          document[(prefix === "" ? "v" : prefix + "V") + "isibilityState"] + "</b><br />";
      }

      function testPageVisibilityApi() {
        if (prefix === null)
          document.getElementById("log").innerHTML = "Your browser does not support Page Visibility API";
        else {
          document.addEventListener(prefix + "visibilitychange", countView);
          countView();
        }
      }

      var views = 0;
      var prefix = getPrefix();
      window.onload = testPageVisibilityApi;
    </script>
  </head>
  <body>
    <p id="log"></p>
  </body>
</html>

Some other good examples can be found at the Mozilla Developer Network.

Conclusion

In this article, I’ve demonstrated what Page Visibility APIs are and how you can use them. The intentions of the people at W3C to help mobile devices — and not just to save resources and connectivity bandwidth — are really worthy, and I hope to see them widely used as soon as possible.

As you’ve seen, the APIs are quite simple and consist of just two properties and a single event, so you can start using them within few minutes to improve your web applications.

Currently, however, they aren’t really reliable due to their poor support among browsers, so you have to use a polyfill.

If you’re into JavaScript APIs, check out our APIs section on the latest site on the SitePoint network … JSPro.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Marco

    var browserPrefixes = ["h", "webkitH","mozH","msH","oH"];
    for(var i = 0; i < browserPrefixes.length; i++) {
    if (document[browserPrefixes[i] + "idden"] != undefined)
    // here goes the code
    }

    • http://www.audero.it/ Aurelio De Rosa

      Yes, you could write the code in that way but I didn’t because I don’t like to mix vendor prefixes with the properties name. I think it’s more clear.

  • http://www.revitalagency.com Revital Agency

    I couldn’t agree with you more. Web apps are becoming more efficient feature rich everyday. I am using the Geolocation API in conjunction with the Google Places API to create a mobile/desktop checkin app, http://www.nightcrawlerz.com . I like what you have here, I think I can use it in my app.

    • http://www.audero.it/ Aurelio De Rosa

      I’m glad you liked the article. If you’ll really use the API in your app, don’t forget to include a polyfill to support the browsers that don’t have implemented Page Visibility API yet.

  • http://www.specrunner.net Marcel

    Enjoyed reading that article. I’m sure it will come in handy some time in the future.