Mobile
 

Mobile Web Apps : Loading Pages (part 1) - Swapping Pages & Fading with WebKit Animations

1/28/2012 5:45:38 PM
Our client is unlikely to be impressed with the web-like page reloads that currently exist in our app, so we need a way to hide the request/response cycle from the user. There are three main ways we can do this:
  1. putting everything on one page, and then hiding and displaying sections as required

  2. loading in new pages via Ajax

  3. including only the complete skeleton of the app up front, and then bringing in data as required

1. Swapping Pages

If all our content is loaded in a single HTML page, a “page” from the point of view of our application is no longer a full HTML document; it’s merely a DOM node that we’re using as a container. We need to choose a suitable container and an appropriate way to group our pages, so that our scripts can manipulate them consistently.

We’ll start by creating a container <div> (called <pages>), which contains a number of child <div> elements that are the actual pages. There can only be one page visible at a time, so we’ll give that element a <class> of <current>. This <class> will be passed to whichever page is the active one:

<div id="pages">
<div id="page-spots" class="current">
<!-- Spots Index -->
</div>
<div id="page-spot">
<!-- Spot Detail -->
</div>
<div id="page-sightings">
<!-- Add Sighting Form -->
</div>
<div id="page-stars">
<!-- Stars Index -->
</div>
<div id="page-star">
<!-- Star Detail -->
</div>
</div>

This list of pages will sit below the tab bar—so no need to change the markup of our navigation. We have, however, hooked up the links to point to the various sections by way of their <id> attributes; this will let us use a sneaky trick to show pages in the next step:

<ul id="tab-bar">
<li>
<a href="#spots">Spots</a>
</li>
<li>
<a href="#sightings">Add a sighting</a>
</li>
<li>
<a href="#stars">Stars</a>
</li>
</ul>

After this, we need a couple of styles for hiding and showing pages. In our markup, every page is a first-level child of the main #pages container, so we can rely on that fact and use a child selector (>). First, we’ll hide all the pages; then we’ll unhide the page that has the <current> <class>:

listing 1. stylesheets/transitions.css (excerpt)
#pages > div {
display: none;
}
#pages > div.current {
display: block;
}

To actually select some pages, we need to intercept the navigation menu clicks. We’ll be using the code we wrote earlier to capture the event and prevent the browser from navigating to the link:

listing 2. javascripts/ch4/07-swap.js (excerpt)
$("#tab-bar a").bind('click', function(e) {
e.preventDefault();
// Swap pages!
});

And here’s the trick: the links point to our page elements by using the anchor syntax of a hash symbol (#), followed by a fragment identifier. It coincidently happens that jQuery uses that exact same syntax to select elements by <id>, so we can funnel the hash property of the click event directly into jQuery to select the destination page. Very sneaky:

listing 3. javascripts/ch4/07-swap.js (excerpt)
$("#tab-bar a").bind('click', function(e) {
e.preventDefault();
var nextPage = $(e.target.hash);
$("#pages .current").removeClass("current");
nextPage.addClass("current");

});

With the target page acquired, we can hide the current page by removing the <current> <class> and passing it to the destination page. Swapping between pages now works as expected, but there’s a slight problem: the selected icon in the tab bar fails to change when you navigate to another page. Looking back at our CSS, you’ll remember that the tab bar’s appearance is due to a <class> set on the containing <ul> element; it’s a <class> that’s the same as the current page <div> element’s <id>. So all we need to do is slice out the hash symbol from our string (using slice(1) to remove the first character), and set that as the <ul>’s <class>:

listing 4. javascripts/ch4/07-swap.js (excerpt)
$("#tab-bar a").bind('click', function(e) {
e.preventDefault();
var nextPage = $(e.target.hash);
$("#pages .current").removeClass("current");
nextPage.addClass("current");
$("#tab-bar").attr("className", e.target.hash.slice(1));
});

2. Fading with WebKit Animations

The page swap we just implemented is as straightforward as it gets. This has its advantages—it stays out of our users’ way, for one. That said, well-placed transitions between pages not only make your apps sexier, they can provide a clear visual cue to the user as to where they’re being taken.

After the original iPhone was released, web developers leapt to re-implement the native transition effects in JavaScript, but the results were less than ideal, often containing lags and jumps that were very noticeable and distracting to users. The solution largely was to ditch JavaScript for moving large DOM elements, and instead turn to the new and hardware-accelerated CSS3 transitions and animations.

Before we worry about the transitions, though, we need to lay some groundwork. To fling DOM elements around, we need to be able to show, hide, and position them at will:

listing 5. stylesheets/transitions.css (excerpt)
#pages {
position: relative;
}
#pages > div {
display:none;
position: absolute;
top: 0;
left: 0;
width: 100%;
}

By positioning the elements absolutely, we’ve moved every page up into the top-left corner, giving us a neat stack of invisible cards that we can now shuffle around and animate. They’re not all invisible, though; remember that in our HTML, we gave our default page the <class> of <current>, which sets its display property to block.

The difference this time is that we’re going to apply CSS animations to the pages. The incoming (new) page, and the outgoing (current) page will have equal but opposite forces applied to them to create a smooth-looking effect. There are three steps required to do this:

  1. Set up the CSS animations.

  2. Trigger the animation by setting the appropriate classes on the pages.

  3. Remove the non-required classes when the animation is finished, and return to a non-animating state.

Let’s start on the CSS. There are many approaches you can take with the problem of emulating native page transitions. We’ll adopt a flexible method that’s adapted from the jQTouch library. This is a modular approach, where we control transitions by applying and removing the relevant parts of an animation to each page.

Before we dive into that, though, a quick primer on CSS3 animations. These are currently supported only in WebKit browsers with -webkit- vendor prefixes. A CSS3 animation is made up of a series of keyframes grouped together as a named animation, created using the @-webkit-keyframes rule. Then we apply that animation to an element using the -webkit-animation-name property. We can also control the duration and easing of the animation with the -webkit-animation-duration and -webkit-animation-timing-function properties, respectively. If you’re new to animations, this is probably sounding more than a little confusing to you right now; never mind, once you see it in practice, it’ll be much clearer.

So let’s apply some animations to our elements. First up, we’ll set a timing function and a duration for our animations. These dictate how long a transition will take, and how the pages are eased from the start to the end point:

listing 6. stylesheets/transitions.css (excerpt)
.in, .out {
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-duration: 300ms;
}

We’ve placed these properties in generic classes, so that we can reuse them on any future animations we create.

Next, we need to create our keyframes. To start with, let’s simply fade the new page in:

listing 7. stylesheets/transitions.css (excerpt)
@-webkit-keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}

In the above rule, fade-in is the name of the animation, which we’ll refer to whenever we want to animate an element using these keyframes. The from and to keywords allow us to declare the start and end points of the animation, and they can include any number of CSS properties you’d like to animate. If you want more keyframes in between the start and end, you can declare them with percentages, like this:

listing 8. stylesheets/transitions.css (excerpt)
@-webkit-keyframes fade-in-out {
from { opacity: 0; }
50% { opacity: 1; }
to { opacity: 0; }
}

With our keyframes declared, we can combine them with the previous direction classes to create the final effect. For our fade, we’ll use the animation we defined above, and also flip the z-index on the pages to make sure the correct page is in front:

listing 9. stylesheets/transitions.css (excerpt)
.fade.in {
-webkit-animation-name: fade-in;
z-index: 10;
}
.fade.out {
z-index: 0;
}

By declaring -webkit-animation-name, we’re telling the browser that as soon as an element matches this selector, it should begin the named animation.

With this CSS in place, we can move to step two. We’ll start by applying our animation to a single navigation item, then broaden it out later so that it will work for our whole tab bar.

The page we’re fading to (#sightings) will need to have three different classes added to it: <current> to make the page visible, <fade> to add our animation, and <in> to apply our timing function and duration. The page we’re fading from (#spots) is visible, so it will already have the <current> <class>; we only need to add the <fade> and <out> classes:

var fromPage = $("#spots"),
toPage = $("#sightings");

$("#tab-sighting a").click(function(){
toPage.addClass("current fade in");
fromPage.addClass("fade out");
});

This gives us a nice fading effect when we click on the “Add a sighting” tab, but now the pages are stuck—stacked atop one another. This is because those <class> names are still there, so the pages now have <current> and they’re both visible. Time to remove them! We’ll do this by binding to the webkitAnimationEnd event, which fires when the transition is complete. When this event fires, we can remove all three classes from the original page, and the <fade> and <in> classes from the new page. Additionally, we must remember to unbind the webkitAnimationEnd event so that we don’t go adding on extra handlers the next time we fade from the page:

var fromPage = $("#spots"),
toPage = $("#sightings");

$("#tab-sighting a").click(function(){
toPage
.addClass("current fade in")
.bind("webkitAnimationEnd", function(){
// More to do, once the animation is done.
fromPage.removeClass("current fade out");
toPage
.removeClass("fade in")
.unbind("webkitAnimationEnd");
})
;
fromPage.addClass("fade out");
});

There we go. Our page is now fading nicely; however, there are a few problems with our code. The first is structural. It will become quite ugly if we have to replicate this same click handler for each set of pages we want to transition to! To remedy this, we’ll make a function called transition() that will accept a page selector and fade from the current page to the new one provided.

While we’re at it, we can replace our bind() and unbind() calls with jQuery’s one() method. This method will accomplish the same task—it binds an event, and then unbinds it the first time it’s fired—but it looks a lot cleaner:

listing 10. javascripts/ch4/08-fade.js (excerpt)
function transition(toPage) {
var toPage = $(toPage),
fromPage = $("#pages .current");

toPage
.addClass("current fade in")
.one("webkitAnimationEnd", function(){
fromPage.removeClass("current fade out");
toPage.removeClass("fade in")
});
fromPage.addClass("fade out");
}


Warning:

Generalizing Functions

You might spy that we’ve hardcoded the current page selector inside our function. This makes our code smaller, but reduces the reusability of the function. If you are building a larger framework intended for more general use, you’d probably want to accept the fromPage as a parameter, too.


Great. Now we have a reusable function that we can employ to fade between any of the pages in our app. We can pull the link targets out of the tab bar the same way we did earlier, and suddenly every page swap is a beautiful fade:

listing 10. javascripts/ch4/08-fade.js (excerpt)
$("#tab-bar a").click(function(e) {
e.preventDefault();
var nextPage = $(e.target.hash);
transition(nextPage);
$("#tab-bar").attr("className", e.target.hash.slice(1));
});

There’s still a major problem, though, and it’s one you’ll notice if you try to test this code on a browser that lacks support for animations, such as Firefox. Because we’re relying on the webkitAnimationEnd event to remove the <current> <class> from the old page, browsers that don’t support animations—and therefore never fire that event—will never hide the original page.


Tip:

Browser Testing

This bug—which would render the application completely unusable on non-WebKit browsers— highlights the importance of testing your code on as many browsers as possible. While it can be easy to assume that every mobile browser contains an up-to-date version of WebKit (especially if you own an iPhone or Android), the real mobile landscape is far more varied.


This problem is easy enough to solve. At the end of our transition() function, we’ll drop in some feature detection code that will handle the simplified page swap in the absence of animations:

listing 11. javascripts/ch4/08-fade.js (excerpt)
function transition(toPage) {

// For non-animatey browsers
if(!("WebKitTransitionEvent" in window)){
toPage.addClass("current");
fromPage.removeClass("current");
return;
}

}

With this code in place, our app now produces our beautiful fade transition on WebKit browsers, but still swaps pages out effectively on other browsers.

There’s still one slightly buggy behavior, one you might notice if you become a little excited and start clicking like crazy. If you click on the link to the current page—or if you tap quickly to start an animation when the previous one has yet to complete—the <class> attributes we’re using to manage the application’s state will be left in an inconsistent state. Eventually, we’ll end up with no pages with the <current> <class>—at which point we’ll be staring at a blank screen.

It’s relatively easy to protect against these cases. We just need to ensure our toPage is different from our fromPage, and that it doesn’t already have the <current><class> on it. This safeguard goes after the variable declaration, and before any class manipulations:

listing 12. javascripts/ch4/08-fade.js (excerpt)
function transition(toPage) {
var toPage = $(toPage),
fromPage = $("#pages .current");

if(toPage.hasClass("current") || toPage === fromPage) {
return;
};



 
Others
 
- Personalize & Secure Your iPad : Adjusting Sounds on your iPad & Personalize Your Picture Frame
- Personalize & Secure Your iPad : Changing your Lock Screen and Home Screen Wallpapers
- Using Media in XNA Game Studio : Media Enumeration
- Using Media in XNA Game Studio : What Is Media?
- Android Application Development : Layouts (part 2) - AbsoluteLayout & RelativeLayout
- Android Application Development : Layouts (part 1) - inearLayout
- Building an Advanced Java Game on Symbian OS (part 4) - Using the Bluetooth API
- Building an Advanced Java Game on Symbian OS (part 3) - Using the Mobile 3D Graphics API
- Building an Advanced Java Game on Symbian OS (part 2) - Using the Mobile Media API & Using the Scalable 2D Vector Graphics API
- Building an Advanced Java Game on Symbian OS (part 1)
- jQuery 1.3 : Simultaneous versus queued effects (part 2) - Working with multiple sets of elements & Callbacks
- jQuery 1.3 : Simultaneous versus queued effects (part 1) - Working with a single set of elements
- iPhone 3D Programming : Crisper Text with Distance Fields (part 3) - Implementing Outline, Glow, and Shadow Effects
- iPhone 3D Programming : Crisper Text with Distance Fields (part 2) - Smoothing and Derivatives
- iPhone 3D Programming : Crisper Text with Distance Fields (part 1) - Generating Distance Fields with Python
- Mapping Well-Known Patterns onto Symbian OS : Singleton
- Mapping Well-Known Patterns onto Symbian OS : Model–View–Controller
- The Anatomy of a Mobile Site : STYLING WITH CSS - CSS Considerations for Mobile & Optimizing CSS
- The Anatomy of a Mobile Site : INVOKING OTHER DEVICE CAPABILITIES & THE STATE OF JAVASCRIPT
- iPad Development : The Dual-Action Color Popover (part 3) - Serving Two Masters
 
 
Most View
 
- Learning about Hyper-V for Windows 8 (part 3) - What you need to know to succeed
- Installing Exchange 2013 : Namespace planning
- Microsoft Access 2010 : Introduction to Relational Database Design (part 2) - Normalization and Normal Forms
- Windows 7 : Updating Software - How to Troubleshoot Problems Installing Updates
- Windows Server 2012 : Installing and Configuring FTP Services (part 4) - Configuring FTP 8 Features and Properties - FTP Directory Browsing Feature Page
- Windows Server 2012 : Hyper-V - Managing Virtual Machines and Virtual Disks (part 1) - Live-Migrating Virtual Machines
- Windows Server 2012 : Increase scalability and performance (part 3) - Offloaded Data Transfer, Support for 4 KB sector disks
- Windows 8 : Windows Store (part 2) - To search Windows Store, To install Windows Store apps
- Microsoft SQL Server 2012 : Recovery Operations (part 3) - Restoring Individual Pages
- Windows 8 : Sharing and Securing with User Accounts - Creating and Managing User Accounts (part 6) - Navigating through user account pages
 
 
Top 10
 
- Sharepoint 2013 : Developing Integrated Apps for Office and Sharepoint Solutions - The New App Model for Office
- Overview of Oauth in Sharepoint 2013 : Application Authorization - On-Premises App Authentication with S2S
- Overview of Oauth in Sharepoint 2013 : Application Authorization - Requesting Permissions Dynamically
- Microsoft Excel 2010 : Working with Graphics - Inserting a Diagram,Inserting an Object
- Microsoft Excel 2010 : Working with Graphics - Inserting WordArt, Using Smart Art in Excel
- Microsoft Excel 2010 : Working with Graphics - Using AutoShapes
- Overview of Oauth in Sharepoint 2013 : Application Authentication (part 2) - Managing Tokens in Your Application
- Overview of Oauth in Sharepoint 2013 : Application Authentication (part 1) - Using TokenHelper
- Overview of Oauth in Sharepoint 2013 : Creating and Managing Application Identities
- Overview of Oauth in Sharepoint 2013 : Introduction to OAuth