Wednesday, September 23, 2009

Gmail for Mobile HTML5 Series: CSS Transforms and Floaty Bars

On April 7th, Google launched a new version of Gmail for mobile for iPhone and Android-powered devices. We shared the behind-the-scenes story through this blog and decided to share more of what we've learned in a brief series of follow-up blog posts. This week, I'll talk about different ways to animate the floaty bar.

Even from the earliest brainstorming days for our new version of Gmail for iPhone and Android-powered devices, we knew we wanted to try something novel with menu actions: a context-sensitive, always-accessible UI element that follows conveniently as a user scrolls. Thus, the "floaty bar" was born! It took us a surprisingly long time, experimenting with different techniques and interactions, to converge on the design you see today. Let's look under the covers to see how the animation is achieved. You may be surprised to find that the logic is actually quite simple!


Screenshots of the floaty bar in action

In CSS:
.CSS_FLOATY_BAR {
...
top: -50px; /* start off the screen, so it slides in nicely */
-webkit-transition: top 0.2s ease-out;
...
}
In JavaScript:
// Constructor for the floaty bar
gmail.FloatyBar = function() {
this.menuDiv = document.createElement('div');
this.menuDiv.className = CSS_FLOATY_BAR;
...
};

// Called when it's time for the floaty bar to move
gmail.FloatyBar.prototype.setTop = function() {
this.menuDiv.style.top = window.scrollY + 'px';
};

// Called when the floaty bar menu is dismissed
gmail.FloatyBar.prototype.hideOffScreen = function() {
this.menuDiv.style.top = '-50px';
};

gmail.floatyBar = new gmail.FloatyBar();

// Listen for scroll events on the top level window
window.onscroll = function() {
...
gmail.floatyBar.setTop();
...
};
The essence here is that when the viewport scrolls, the floaty bar 'top' is set to the new viewport offset. The -webkit-transition rule specifies the animation parameters. (The 'top' property is to be animated, over 0.2s, using the ease-out timing function.) This is the animation behavior we had at launch, and it works just fine on Android and mobile Safari browsers.

However, there's actually a better way to achieve the same effect, and the improvement is particularly evident on mobile Safari. The trick is to use "CSS transforms". CSS transforms is a mechanism for applying different types of affine transformations to page elements, specified via CSS. We're going to use a simple one which is translateY. Here's the same logic, updated to use CSS transforms.

In CSS:
.CSS_FLOATY_BAR {
...
top: -50px; /* start off the screen, so it slides in nicely */
-webkit-transition: -webkit-transform 0.2s ease-out;
...
}
In JavaScript:
// Called when it's time for the floaty bar to move
gmail.FloatyBar.prototype.setTop = function() {
var translate = window.scrollY - (-50);
this.menuDiv.style['-webkit-transform'] = 'translateY(' + translate + 'px)';
};

// Called when the floaty bar menu is dismissed
gmail.FloatyBar.prototype.hideOffScreen = function() {
this.menuDiv.style['-webkit-transform'] = 'translateY(0px)';
};
Upon every scroll event, the floaty bar is translated vertically to the new viewport offset (modulo the offscreen offset which is important to the floaty bar's initial appearance). And, why exactly is this such an improvement? Even though the logic is equivalent, iPhone OS's implementation of CSS transforms is "performance enhanced", whilst our first iteration (animating the 'top' property) is performed by the OS in software. That's why the experience was unfortunately somewhat chunky at times, depending on the speed of the iPhone hardware.

You'll see smoother looking floaty bars coming very soon to an iPhone near you. This is just the first in a series of improvements we're planning for the mobile Gmail floaty bar. Watch for them in our iterative webapp, rolling out over the next couple of weeks and months!



Previous posts from Gmail for Mobile HTML5 Series:
HTML5 and Webkit pave the way for mobile web applications
Using AppCache to launch offline - Part 1
Using AppCache to launch offline - Part 2
Using AppCache to launch offline - Part 3
A Common API for Web Storage
Suggestions for better performance
Cache pattern for offline HTML5 web application
Using timers effectively
Autogrowing Textareas
Reducing Startup Latency

4 comments:

  1. Asked this in a couple of other places but not had a response yet so will try here. Has anyone managed to get the gmail mobile app on Android to work in offline mode? If I try and send a mail when in Airplane mode it just hangs and then gives a connection error.
    (On an Canadian HTC Magic 1.5)

    ReplyDelete
  2. Having written a basic javascript animation framework, I know how useful it can be to animations in web apps. However, in this situation, I would find it more useful for the floaty bar to stay in a fixed position when a checkbox is enabled.
    e.g.

    // Constructor for the floaty bar
    gmail.FloatyBar = function() {
    this.menuDiv = document.createElement('div');
    this.menuDiv.className = CSS_FLOATY_BAR;
    this.menuDiv.style.position = 'fixed';
    ...
    };

    // Called when it's time for the floaty bar to move
    gmail.FloatyBar.prototype.setTop = function() {
    //ensure floaty bar doesn't cover up links on top of app
    if( window.scrollY < 30 ){
    this.menuDiv.style.position = 'absolute';
    this.menuDiv.style.top = '30px';
    }
    };

    // Called when the floaty bar menu is dismissed
    gmail.FloatyBar.prototype.hideOffScreen = function() {
    this.menuDiv.style.top = '-50px';
    };

    gmail.floatyBar = new gmail.FloatyBar();

    // Listen for scroll events on the top level window
    window.onscroll = function() {
    ...
    gmail.floatyBar.setTop();
    ...
    };

    This is just a suggestion, but it would prevent the floaty bar from disappearing when you scroll down and covering emails when you scroll up.

    ReplyDelete
  3. Scott, I don't know if your suggestion would work. Have you tried it out? It's not easy to create a fixed bar. I think you're actually fixing your bar to the page and not the viewport. Thus your bar will not appear 'fixed' as the user scrolls and the viewport moves down the page.

    The google code could be improved by using the translate3d function instead of translateY. Here's an explanation why: http://groups.google.com/group/iphonewebdev/msg/3146bff39e4eb5cd?pli=1

    But it probably wouldn't make a noticeable difference...

    ReplyDelete
  4. This is INVALUABLE information. Joanne, thank you so much for sharing!

    ReplyDelete