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 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.
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
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:
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.
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:
Handling one or more touches:
You can access an array of current touches from event.originalEvent.touches.
For one touch, event.originalEvent.changedTouches.
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
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.