JavaScript
Article

The Screen Orientation API Reloaded

By Aurelio De Rosa

In September I published an article titled Introducing the Screen Orientation API that, not surprisingly, discussed the Screen Orientation API. This API provides the ability to read the screen orientation state, to be informed when this state changes, and to be able to lock the screen orientation to a specific state. As I mentioned in the article, the Screen Orientation API is at a very early stage, as it’s a W3C Working Draft. The current specifications may be superseded in a few months by a newer version. Guess what? It happened! In fact, the last version of Chrome (38) and Opera (25) support the new version of the specifications (Firefox up to 33 still supports the old one).

In this article I’m going to highlight the non-trivial differences between these two versions of the specifications, so that you can stay up to date.

What is the Screen Orientation API?

Before we start delving into the new specifications I want to offer you a brief recap of what this API is and why you may want to use it. The Screen Orientation API provides you with the ability to detect the orientation of a user’s device (in terms of portrait and landscape) and lock in the mode your application needs. The specification states that as a security condition a user agent (read browser) may allow the lock of the screen only if the page is in fullscreen mode. As of today, all modern browsers require you to do so, therefore you should keep this point in mind if you want to employ the Screen Orientation API.

If you’re a follower of my articles and you remember the previous one on this API, I’m sorry to inform you that only the general concepts are the same and that almost everything has changed. Specifically, all the methods, properties, and events have been moved under a new object and/or renamed. Don’t be scared though, I’ll explain everything you need.

Properties

In the previous version, the API was exposed through the window.screen property. The new version has moved it all inside the orientation property that belongs to window.screen. In the current version orientation is an object and not the property that returned the orientation of the device as it used to be.

This property contains the following read-only properties:

  • type: A string containing the current orientation of the device (more on its possible values in a moment).
  • angle: A number specifying the current orientation angle of the device.

type can assume one of the following values:

  • portrait-primary: The orientation is in the primary portrait mode. For a smartphone this values means that it’s in a vertical position with the buttons at the bottom.
  • portrait-secondary: The orientation is in the secondary portrait mode. For a smartphone this value means that it’s in a vertical position with the buttons at the top (the device is down under)
  • landscape-primary: The orientation is in the primary landscape mode. For a smartphone this value means that it’s in a horizontal position with the buttons at the right.
  • landscape-secondary: The orientation is in the secondary landscape mode. For a smartphone this value means that it’s in a horizontal position with the buttons at the left.

It would seem natural to think that a device has an angle of 0 when it’s in portrait-primary mode, but this isn’t always the case. The specifications recommend to never assume any cross-devices relationship between the screen orientation type and the screen orientation angle, because an angle of 0 means only the device is in its natural orientation. For example a smartphone in portrait mode with the buttons orientated toward the ground.

Methods

In the previous specifications we had lockOrientation() and unlockOrientation(). In the new one they have been renamed as lock() and unlock() respectively. Not surprisingly, they perform the same actions. lock() locks the device’s screen in one or more orientations (depending on the parameter), while unlock() unlocks the screen.

An example of use of these two methods is shown below:

// Lock the screen in landscape-primary mode
screen.orientation.lock('landscape-primary');

// Unlock the screen
screen.orientation.unlock();

The result of invoking the first statement of this snippet is shown in the image below:

Device Orientation API in Action

The Screen Orientation API in action. The device is physically in portrait mode but the screen acts as if it was in landscape.

The lock() method is heavily changed, so please read carefully. It accepts only one string to specify the orientations we want to lock the screen at, and the value returned isn’t a Boolean anymore but a Promise object. If you need an intro to JavaScript promises, SitePoint has you covered with An Overview of JavaScript Promises. Finally, there are two new possible values you can pass to lock: any and natural. The complete list of values you can pass is described below:

  • any: The devices is able to be locked in any orientation it can assume. The actual orientations depend on the device, for example the Samsung Galaxy S3 can’t be locked in portrait secondary (upside down).
  • natural: The device is in its natural orientation. For a smartphone this usually means in its primary portrait mode (with the buttons in direction of the ground).
  • portrait-primary: The orientation is in the primary portrait mode. For a smartphone this values means that it’s in a vertical position with the buttons at the bottom.
  • portrait-secondary: The orientation is in the secondary portrait mode. For a smartphone this value means that it’s in a vertical position with the buttons at the top (the device is down under)
  • landscape-primary: The orientation is in the primary landscape mode. For a smartphone this value means that it’s in a horizontal position with the buttons at the right.
  • landscape-secondary: The orientation is in the secondary landscape mode. For a smartphone this value means that it’s in a horizontal position with the buttons at the left.

The unlock() method is used to release a previously set lock and isn’t changed compared to the previous version.

Events

The API also provides an event that is fired every time the screen orientation changes. You can listen for this change event as shown below:

window.screen.orientation.addEventListener('change', function() {
   // do something here...
});

Browser Compatibility

Support for the Screen Orientation API has improved since last month with the release on Chrome 38 and Opera 25. As of today Chrome 38 and Opera 25 implemented the new version of this API without any vendor prefix only in their mobile version: Chrome for Android and Opera for Android. Firefox, up to version 33 (the last at the time of this article), supports the old version of the specifications using its vendor prefix (moz). Internet Explorer 11 supports the old version as well using its vendor prefix (ms).

Please note that at moment Firefox exposes two issues that cause the browser to crash. You can find more information at https://bugzil.la/1061372 and https://bugzil.la/1061373.

Because the two versions of the specifications are so different, you have to learn how to detect for both of them. The following code shows how to do that:

var orientation = screen.orientation    ||
                  screen.mozOrientation ||
                  screen.msOrientation  ||
                  null;

if (orientation === null) {
   // API not supported
} else if (typeof orientation === 'object') {
   // new versions supported
} else {
   // old versions supported
}

Demo

The demo we’re going to develop is a refactored version of the one we built in my previous article. It consists of an HTML page that displays text indicating the current orientation of the screen and the angle where supported. Then, you can choose the orientation you want to lock your screen’s device at through the use of a select box. As you can see the select box also contain values not supported in the old version. These values will be deleted via JavaScript if the browser supports the old version.

Inside the JavaScript code we detect if the browser supports this API or not. In addition we’ll check what version and what prefix it uses, if any. In this demo we’ll also employ the Fullscreen API for the reasons explained earlier in this article.

You can find the complete code for the demo below but you can also play with it online.

<!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>Screen Orientation 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;
         }

         .api-support
         {
            display: block;
         }

         .hidden
         {
            display: none;
         }

         .value
         {
            font-weight: bold;
         }

         .button-demo
         {
            padding: 0.5em;
            margin: 1em;
         }

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

      <span id="so-unsupported" class="api-support hidden">API not supported</span>
      <span id="soo-supported" class="api-support hidden">Old API version supported</span>

      <div id="so-results">
         <ul>
            <li>
               The orientation of the device is <span id="orientation" class="value">unavailable</span>.
            </li>
            <li class="new-api hidden">
               The angle of the device is <span id="angle" class="value">unavailable</span>.
            </li>
         </ul>

         <form>
            <label for="orientation-type">Lock the device in:</label>
            <select id="orientation-type">
               <option value="any">any</option>
               <option value="natural">natural</option>
               <option value="portrait">portrait</option>
               <option value="landscape">landscape</option>
               <option value="portrait-primary">portrait-primary</option>
               <option value="portrait-secondary">portrait-secondary</option>
               <option value="landscape-primary">landscape-primary</option>
               <option value="landscape-secondary">landscape-secondary</option>
            </select>
            <br />
            <input class="button-demo" id="lock-button" type="submit" value="Lock!" />
            <input class="button-demo" id="unlock-button" type="reset" value="Unlock!" />
         </form>
      </div>

      <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>
         var prefix = 'orientation' in screen ? '' :
                      'mozOrientation' in screen ? 'moz' :
                      'msOrientation' in screen ? 'ms' :
                      null;

         if (prefix === null) {
            document.getElementById('so-unsupported').classList.remove('hidden');

            ['lock-button', 'unlock-button'].forEach(function(elementId) {
               document.getElementById(elementId).setAttribute('disabled', 'disabled');
            });
         } else {
            var select = document.getElementById('orientation-type');
            var orientationElem = document.getElementById('orientation');
            var onChangeHandler;

            var Fullscreen = {
               launch: function(element) {
                  if(element.requestFullscreen) {
                     element.requestFullscreen();
                  } else if(element.mozRequestFullScreen) {
                     element.mozRequestFullScreen();
                  } else if(element.webkitRequestFullscreen) {
                     element.webkitRequestFullscreen();
                  } else if(element.msRequestFullscreen) {
                     element.msRequestFullscreen();
                  }
               },
               exit: function() {
                  if(document.exitFullscreen) {
                     document.exitFullscreen();
                  } else if(document.mozCancelFullScreen) {
                     document.mozCancelFullScreen();
                  } else if(document.webkitExitFullscreen) {
                     document.webkitExitFullscreen();
                  } else if (document.msExitFullscreen) {
                     document.msExitFullscreen();
                  }
               }
            };

            // Determine what version of the API is implemented
            if ('orientation' in screen && 'angle' in screen.orientation) {
               // The browser supports the new version of the API

               // Show the properties supported by the new version
               var newProperties = document.getElementsByClassName('new-api');
               for(var i = 0; i < newProperties.length; i++) {
                  newProperties[i].classList.remove('hidden');
               }

               document.getElementById('lock-button').addEventListener('click', function (event) {
                  event.preventDefault();
                  Fullscreen.launch(document.documentElement);
                  screen.orientation.lock(select.value);
               });

               document.getElementById('unlock-button').addEventListener('click', function (event) {
                  event.preventDefault();
                  Fullscreen.exit();
                  screen.orientation.unlock();
               });

               var angleElem = document.getElementById('angle');
               onChangeHandler = function() {
                  orientationElem.textContent = screen.orientation.type;
                  angleElem.textContent = screen.orientation.angle;
               };
               screen.orientation.addEventListener('change', onChangeHandler);
               onChangeHandler();
            } else {
               // The browser supports the old version of the API, so the user is informed of that
               document.getElementById('soo-supported').classList.remove('hidden');

               // Remove the options that aren't available in the old version of the API
               var unavailableOptions = [
                  document.querySelector('#orientation-type option[value="any"]'),
                  document.querySelector('#orientation-type option[value="natural"]')
               ];
               for(var i = 0; i < unavailableOptions.length; i++) {
                  unavailableOptions[i].parentElement.removeChild(unavailableOptions[i]);
               }

               document.getElementById('lock-button').addEventListener('click', function (event) {
                  event.preventDefault();
                  Fullscreen.launch(document.documentElement);

                  setTimeout(function () {
                     screen[prefix + (prefix === '' ? 'l' : 'L') + 'ockOrientation'](select.value);
                  }, 1);
               });
               document.getElementById('unlock-button').addEventListener('click', function (event) {
                  event.preventDefault();
                  screen[prefix + (prefix === '' ? 'u' : 'U') + 'nlockOrientation']();
                  Fullscreen.exit();
               });

               onChangeHandler = function() {
                  var orientationProperty = prefix + (prefix === '' ? 'o' : 'O') + 'rientation';
                  orientationElem.textContent = screen[orientationProperty];
               };
               screen.addEventListener(prefix + 'orientationchange', onChangeHandler);
               onChangeHandler();
            }
         }
      </script>
   </body>
</html>

Conclusion

In this article I described the new version of the Screen Orientation API specification. This API enables you to detect the orientation of a user’s device (in terms of portrait and landscape) and lock it in the mode your application needs. As we’ve seen the support has increased lately so you can use it in more browsers, although you have to pay attention to the version of the API supported. Remember, Chrome and Opera support the new version while Firefox and Internet Explorer support the old one.

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.