Developing a Web Application with a native feel for Tablets

The 2PartsMagic team recently completed a project for a client that enables tablet users to download auction data while online, then view and annotate individual lots (with drawing tools, notes and categories), compile and manage shortlists, and send results by email.
Further requirements included:

  • The project must be web application only, not iPad or Android native app.
  • The server provides static directories of data for downloading, responds to calls for latest updates, and receives email data for sending on. Everything else to be handled on the client by Javascript.
  • The application must be useable while offline, and maintain a persistent local data store accessible after device or browser restarts.
  • The web application should feel more like a native app than a webapp.

Notes: We used jQuery in this project, most Javascript examples below require it. Also, our main development testing platform was iPad, so often this will be mentioned and not android for certain parts of the process.
This post is focussed on the user interface of the webapp and our success in making it quick and usable. Development concerning offline data with local storage management, WebSQL, cache manifest and our solutions will not be covered in this post.

User Interface Performance and Responsiveness

We realised early on that we would have to forget the snappy javascript dom manipulation and css animation that has become easy and commonplace over the past couple of years. While the iPad can feel highly responsive, we found out that this speed is only available when iOS acceleration is used. Dom manipulation, except for with very small elements, is slow and a single operation can cause noticeable delay. Similarly for animation using CSS. Anything larger than around fingertip size causes noticeable lag when CSS properties are used for animation.

All of which meant trouble for us. The application’s performance needed to feel as close as possible to a native app, so that functions like touch drawing, pinching, scrolling and switching between lots would not frustrate the user and render the app worthless. After several attempts and some awkward workarounds, we realised that our current methods for this kind of interface and optimising performance needed augmenting. Time for research and a different approach.

The tool that most fitted some of our needs ended up being iScroll4. This helped a great deal in allowing us to use native acceleration to zoom and pan documents smoothly within a fixed user interface. A little tricky to implement at first, but well worth it.
You can find some guidelines on the homepage http://cubiq.org/iscroll-4, but you should really take a look at source code as it is  easy to read and gives you a fuller overview of the properties, events and methods available.
Using iScroll is simple at first:

<div id="wrapper">
   <div id="element-that-moves-with-touch">
      contents
   </div>
</div>
<script type='text/javascript'>
   setTimeout( function () {
   var myScroll = new iScroll ( 'wrapper', {
      vScroll: true,
      hScroll: true,
         zoom: true,
         zoomMax: 1,
         zoomMin: 0.65,
         doubleTapZoom: 1,
         wheelAction: 'zoom'
      });
   }, 5 );
</script>

The timeout is to avoid errors if DOMContentLoaded triggers early.
The homepage documentation will tell you that you need to use the content’s parent element when creating the iScroll instance. What we also found in practice was that we also had to control display sizes for both the parent and content elements for the iScroll behaviour to be predictable and reliable. Unless prevented from exceeding a maximum width, the container element could just expand to accommodate larger contents. And the iScroll functions would not fully kick in until the child contents element got larger than the parent. often if using 2 dimensional scrolling, a moderate amount of fine tuning of CSS size, padding and margin was required to get positioning perfect. In our webapp, CSS sizing of contents had to change for different display widths for the iscroll behaviour to work perfectly. Use window.innerWidth for this.
We needed a background image on the contents elements used here. We found that zooming so that a bitmap image was displayed full screen would dramatically decrease performance as zoom was increased beyond 100%. So that is why here we have a myScroll.zoomMax property of 1, or 100%.
Our interface used fixed absolute positioning. So upon a device reorientation event, we made sure to re-size the container and then force iScroll to re-size to fit its new environment.
Using jQuery,

<body onorientationchange='swapView()' >
   <!-- page contents -->
</body>

<script type='text/javascript'>
   function swapView() {
      $('#wrapper').css({
         width: window.innerWidth + 'px',
         height: window.innerHeight + 'px'
      });
      if (myScroll) {
         setTimeout(function () {
            myScroll.refresh();
         }, 0);
      };
   }
</script>

Again, the timeout allows CSS to update before refreshing the iScroll instance.
Note: iScroll is compatible with later iPhone, Ipod touch, iPad, Android, Desktop Webkit and Firefox browsers. However in our project we found that iPad performance was better than Android. Both fine for simpler pages, but in our relatively complex web application the android performance was only just acceptable.

Events with Touch Controls

Our application required that we handle events reliably on iPad, android and desktop browser. We decided to look at jQuery Mobile as the most obvious library for our application.
jQuery Mobile does provide a way of handling user generated events across all of our target browsers. However, its intended use is as a full web framework, incorporating a user interface and a system for dynamic page loading. This would be fantastic for the right webapp but unfortunately not this one. It simply would not fit with the fixed, document editing style web interface that was required.
So, we ended up implementing our own methods for managing events across tablets and desktop browsers.
Click / press / tap / move events: Use touchstart, touchmove, touchend.
Standard practice has us using click events with the mouse, allowing for cancelling a mousedown by leaving the button. However, on tablets we found click events to be too slow, a few hundred milliseconds at worst. A more responsive event is touchstart, which seems to trigger instantly.
IPads respond to both click and touch events, meaning button code gets executed twice, so we added a test to prevent clicks for tablet only:

<button id='myButton'></button>
<script type='text/javascript'>
   function tapped(event) {
      var touchable = 'ontouchstart' in document.documentElement;
         if((touchable && event.which === 0) || (!touchable && event.which === 1)){
            return true;
         }
      return false;
   }
   $('#myButton').bind('touchstart click', function(event) {
      if(tapped(event)) {
         // button actions;
      }
   });
</script>

Handling one or more touches:
You can access an array of current touches from event.originalEvent.touches.
For one touch, event.originalEvent.changedTouches[0].

<button id='myButton'></button>
<script type='text/javascript'>
   getSingleTouch = function(event){
      var touch;
      if (event.originalEvent.touches==undefined) {
         touch = event; 
      } else {
         touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0];
      }
      return touch;
   }
   $('#myButton').bind('touchstart click', function(event) {
      var touch = getSingleTouch;
      var touchX = touch.pageX;
   });
</script>

We found that the best way to check current touches was to get event.originalEvent.touches.length each time a new touchstart or touchend event was fired.

A webapp that feels like a native app

There are a few changes that you should make to your HTML, and some extra Javascript techniques to use to give your webapp the look and feel of a more robust interface.
Firstly, meta tags:
You can override the iPad operating system’s default browser zoom settings and give custom page sizing using meta tags.

<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='black' />
<meta name='viewport' content='initial-scale=1, maximum-scale=1, user-scalable=no' />

We had to override the zooming system defaults for our app, so scale kept at 1 and user scaling prevented.
Setting apple-mobile-web-app-capable changes the way the webapp functions if a user adds it as an app to their iPad home screen, then runs it in standalone mode. Safari interface elements are not shown, leaving only the top status bar and giving a more native app feel. Setting the bar to display in black rather the standard light colouring helps too.
http://developer.apple.com/library/IOs/#documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html gives more detail on this and configuring iPad loading screen and app icon.
You can inhibit default page panning on a touchmove (drag) event by applying event.preventDefault(); to a page element’s behaviours.
iOS Safari does not support CSS position: fixed property. So our fixed interface elements had to be placed using absolute positioning depending on the current window dimensions.

Bookmark and Share
This entry was posted in All and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">

What is 6 + 12 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)