Design and performance can make or break your app, so in today's consumer market it is essential to focus on both. In this article I will demonstrate a technique for enhancing the graphical performance of your web app by unlocking the potential of the graphical processing unit (GPU).
While native apps still out-perform web apps in some areas, including animation smoothness, it is safe to say web apps have come a long way, and they have a bright future before them. As browsers are enabling access to more and more device hardware, the performance of web apps can be leveraged more then ever before.
Most modern devices have not only a CPU, but also a GPU. The sole purpose of the GPU is to effectively take over from the CPU the burden of heavy calculations involved in graphical rendering.
The latest versions of the popular browsers such as Firefox, Chrome, IE9, Safari and Opera are capable of enabling hardware acceleration. However, most animation is executed from the browser's rendering engine. GPU acceleration is only activated when there is an indication that heavy computation will take place.
GPU acceleration can be triggered when executing the following tasks:
- CSS3 transitions
- CSS3 3D transforms
- Canvas Drawing
- WebGL 3D Drawing
For a most HTML5 apps it is unlikely that you need 3D rendering, Canvas or WebGL capabilities. Nevertheless, if you are looking to simulate the smoothness of native app animation you can still trick the browser into enabling GPU rendering.
The strongest CSS indication for the browser to enable hardware acceleration is that of 3D Transformation. To activate the GPU for compositing a HTML element we can apply the following CSS rule on an element.
transform: transale3d(0,0,0);
That sounds great! But how will this be useful for us if we just want to slide pages around?
Let's build something and see it working in action.
The following code example will simulate a multi-pane layout. In a real production environment many things would be done differently, but this example should give you an idea how to turn on the browser's HTML5 capability to make the GPU work for you in order to achieve smoother animation when sliding the pages.
A multi-pane layout is a common user interface pattern that has the capability to deal with different screen sizes, providing a balanced and aesthetically pleasing layout. It is capable of combining multiple views into one for smaller screens while providing a compound view for screens with more real estate.
Let's build the following multi-pane layout, inspired by the native Gmail app for the Google Nexus 7” device.
Note: The following code example is optimized for Firefox and Chrome browser.
Let's start with the basic structure and content, in my example it will be static HTML, two lists that could be generated server side or from a client side templating framework.
<div class="main-container"> <ul class="title_list accelerated"> <li class="first"> <a data-id="#element_1" href="https://www.urbaninsight.com/" class="selected"> <h4>Element 1</h4> <p>Element 1 short description </p> </a> </li> <li> <a data-id="#element_2" href="https://www.urbaninsight.com/"> <h4>Element 2</h4> <p>Element 2 short description </p> </a> </li> <li> <a data-id="#element_3" href="https://www.urbaninsight.com/"> <h4>Element 3</h4> <p>Element 3 short description </p> </a> </li> </ul> <ul class="detailed_list accelerated"> <li id="element_1" class="first"> <h2>Element 1 detail page</h2> <p>Etiam luctus ...</p> </li> <li id="element_2"> <h2>Element 2 detail page</h2> <p>Duis auctor...</p> </li> <li id="element_3"> <h2>Element 3 detail page</h2> <p>Lorem ipsum ... </p> </li> </ul> </div>
When deciding how your HTML markup should be generated, keep in mind that in order to maximize performance you want GPU acceleration enabled on as few HTML elements as possible. Since we want to make the titles of elements switch places with the descriptions, in our case it made more sense to create two separate lists and enable GPU composition on the lists themselves rather, then to have all the content in one list and accelerate titles and details in each list item.
In the first <ul> we have the list of the titles of the "Elements" with a small description while in the second list we have the detailed information pages. Since we created two lists we need to make the connection between the title and the details page of the element. There is a variety of ways how you can do this, I have used the "data-id" attribute to achieve this.
The basic functionality we are looking to implement in the mobile view is to show the detail page of an element when the title is clicked and to be able to navigate back to the titles list page using a "home button". On a tablet we should be able to click through the elements of the list and make the detail page show the content of the clicked element. We can achieve this by adding the following JavaScript:
<script> $(function() { $('.title_list li a').click(function(event){ event.preventDefault(); event.stopPropagation(); //mark clicked element as selected $('.title_list li a').removeClass('selected'); $(this).addClass('selected'); //when an element was clicked //we want the list of elements to leave the view port //and the details list to come in $('.title_list').removeClass('in_focus').addClass('out_left'); $('.detailed_list').removeClass('out_right').addClass('in_focus'); //we only want to show the details of the clicked element $('.detailed_list li').removeClass('in_focus').addClass('out_right'); // show the detail page of the seleceted var element = $(this).data("id"); $(element).removeClass('out_right').addClass('in_focus'); }); $('#home_button').click(function(event) { event.preventDefault(); event.stopPropagation(); $('.detailed_list').removeClass('in_focus').addClass('out_right'); $('.title_list').removeClass('out_left').addClass('in_focus'); }); }); </script>
As you can see, no animation is being done by jQuery. I am using jQuery only to swap the classes, and the actual animation happens when we swap the classes.
I added click event listeners for the titles and a listener for the home button.
When the click event fires on a title,
- we mark the clicked element as selected
- push the titles list out of focus
- pull the details list in focus
- hide all the detail pages except that of the clicked element
When the home button is clicked
- we pull the detail list out of the view-port
- pull the list of element back in focus
Note how the JavaScript is unaware of the size of the view port, it just applies the same classes to the lists in any case and lets the CSS decide how to deal with them. The classes will gain different meaning for different screen sizes in the CSS file. While decoupling the presentational logic of the of the app into the JavaScript and CSS files is not always wanted, in cases where the only decisive variable is the size of the screen, you will find this approach quite handy.
The key for how the Divs and Lists will behave lies in the CSS, but first we need to define the class for the accelerated HTML elements.
.accelerated { -webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); -moz-transition: all 1s; -webkit-transition: all 1s; -o-transition: all 1s; transition: all 1s ; }
In this case I have used translateZ to enable the acceleration, some older browsers mobile versions do not support 3D transformation or transformation on the Z axis, to support these browsers you can set up a different class using translateX or translateY, however these will not hardware accelerated.
In this example I have gone with the lazy approach of using the same transition speed and timing duration for all the properties of all the accelerated HTML elements, you can define different transitions if you like by adding new classes or just by playing around with the transition property.
The Syntax for CSS3 transition is pretty straight forward:
transition: [ <transition-property> || <transition-duration> || <transition-timing-function> || <transition-delay> ]
[I will talk a bit more on timing functions at the end of this article.]
After we have the class for enabling acceleration and set up the transitions, we set up the media queries and define the end states for the sliding HTML elements.
.title_list li a.selected{ background: url(../img/arrow.png) right center no-repeat; } .detailed_list li.in_focus{ opacity:1; } .detailed_list li.out_right{ opacity:0; } @media all and (max-width: 800px) { .title_list li a.selected{ background: none; } .detailed_list{ left:100%; } .title_list{ left:0; } .detailed_list li{ opacity: 1; } .detailed_list.in_focus, .title_list.in_focus{ left:0; } .detailed_list.out_right{ left:100%; } .title_list.out_left{ left:-100%; } }
We have three states for the sliding lists, in_focus, out_left and out_right since we want to slide the titles list and the details list in two different directions. Defining different style rules in a class selector in one media query than in another will result in different animation on the HTML element for different screen sizes. This way the sliding pages for mobile users can become fixed or tabbed pages for tablet users.
If you wish to keep all the presentational logic in JavaScript you should not use jQuery animate to change the value of the properties; that will not end up accelerated by the GPU. Also you have to deal with specific vendor prefixes separately, the "-vendor-transform : translate3d(0,0,0)" will not be accelerated.
Lets Take a look at the demo, and try resizing the width of your browser below 800px to see the different layouts.
You can also download the zipped code here.
But is it really accelerated?
An easy way to test if GPU has kicked in is to enable displaying the frame rate in chrome web browser for hardware accelerated pages.
To do this simply type “chrome:flags” in Chrome scroll down to the FPS counter, enable it and click on the Relaunch Now button on the bottom of the screen.
After enabling it, you should be able to see the FPS rate displayed in the top left corner of your window, indicating that the page is indeed GPU accelerated.
A few more words about transitions:
In this example I didn’t use a specific easing for the transition; much fun can be had by exploring different timing functions. If the predefined ease-in, ease-out and others satisfy your need that is great, but if you need something more interesting--lets say for simulating a page of a book turn in a 3D animation--you can create your own easing using the cubic-brezier function.
transition: left 500ms cubic-bezier(0.455, 0.030, 0.515, 0.955)
If you are not sure what kind of easing you are looking for, the CEASER CSS easing tool can help you.
Degrading Gracefully
CSS Transitions and transformations degrade gracefully for older browsers so you don’t have to be afraid of using them, but if you want to provide the same animated presentation you can always use jQuery animations. Libraries such as Modernizr can help you decide what to do.
</script> if (Modernizr.csstransforms && Modernizr.csstransitions) { // cool stuff here } else { // not so cool stuff here } </script>
Things to Look Out For When Enabling GPU Acceleration
Using GPU accelerated animations for sliding divs around may be fun, but 2D animations are not accelerated by default for a reason. When hardware gets enabled its pretty obvious that battery consumption will grow, you need to take in consideration device battery limitations.
Hardware acceleration enabled multiple times on an element could end up being worse then not accelerated at all. Important to know which elements are accelerated and why.
There is no guarantee that having a hardware acceleration on an element will always make the animation smoother. Much depends on the browser and the GPU driver of the device. Know your target audience and only use it when you experience real performance advantages.
And most importantly always, always have fun developing it.
I hope this article has given you some insight into how to use CSS transformations and transitions to mimic user interface patterns used for native apps or at least intrigued you enough to further experiment. You're welcome to leave your comments and questions in the comment section below.