iPhone Webapps 101: make your buttons feel native
Wednesday, 14 October 2009
If you have read the first tutorial in this series you’ll know that an iPhone web app is just a regular web page. I explained how you could get rid of some of the default behaviours of Safari and how to make the app run full screen. Unfortunately this is just the beginning. Even if you recreate the look of a native app as accurate as possible, it will still not feel like a native app.
Mouse events on a touch screen
The cause of this problem; mouse events are handled differently on an iPhone. This shouldn’t be a surprise. We don’t use a mouse on the iPhone, we use our finger to tap and move on the screen. The native touch screen events are quite a bit different from the normal mouse events that we are used to.
Luckily normal web developers won’t have to deal with these native touch events. Safari will translate them to regular mouse events. While these translated mouse events are good enough for maintaining compatibility with regular websites, they are too crude for any real interaction.
Take for example a simple tap on the screen. In theory this could translate perfectly to a
mousedown event, a
mouseup event and finally a
click event. The
mousedown event would fire when your finger touches the screen, the
mouseup event will fire when you release your finger. If the
mouseup events have the same target these events are followed by a
click event. This is how it works with a regular mouse and how it ideally would be translated. In reality all three events are dispatched at the same time: after you have released your finger. To make matters worse there is a small delay between releasing your finger and the moment the events are dispatched.
The simultaneous dispatch of the events is a real problem for our buttons. Most native buttons have multiple states. Take for example a simple toolbar button at the top of the screen. If you hold down your finger on the button it will become darker. Or take a look at an icon in the bottom toolbar of the Mail app or the playback controls of the iPod app. If you hold down your finger a white glow around the icon will appear. Normally we would trigger the alternative state using the
mousedown event and restore the original state using the
mouseup event. Unfortunately these states are not possible to recreate with a webapp because the
mouseup events are dispatched at the same time.
Using native touch events
There is one thing we can do to solve this mess. The iPhone will also expose its native touch events to web apps. If we use the native touch events we can detect exactly when the user holds down his finger on a button and when he releases his finger.
The iPhone will dispatch three different touch related events:
touchend. The first event is dispatched when a finger or multiple fingers touch the screen. The second is dispatched when one of those fingers move over the screen and the last event is dispatched when all fingers have left the screen. Without even knowing more about these events we can already make our first prototype.
The idea is to change the state of the button by toggling a class. If the button has no class it will show the regular button, but if the class is active it will show the darker button instead. So all we have to do is set the class to active as soon as we notice that the
touchstart event is dispatched to our button and remove the class as soon as we notice either the
touchmove or the
Use an iPhone or iPod touch to see how the first prototype works.
Dispatching the click event ourselves
There is one more addition we want to make to the prototype. There is still the delay between releasing your finger and dispatching the
click event. Our button feels sluggish compared to a native button. We want to prevent Safari from dispatching its own
click event. It turns that is exactly what will happen if you use
preventDefault() to prevent the default action of the
touchstart event. It will disable Safari’s own conversion of touch events to a
To make our prototype work again we just have to create our own
click event and dispatch it to the button. Of course we only need to do this if our finger hasn’t moved. The easiest way to check this would be to look at the class of our button. If our finger moved we remove the active class. So if the active class is still present we need to dispatch the event and if is not present we can safely ignore the event.
Use an iPhone or iPod touch to see how the second prototype works.
Emulating the behaviour of a native button
The second prototype will actually work quite well, but there are still differences compared to native buttons. If you look closer at native buttons you’ll notice that you can actually move your finger away from the button and it will still retain its active state. There is a 100 pixel radius around the button and only if you move your finger outside of that radius it will loose its active state. Move your finger back into the radius and it will become active again. And even more importantly if you release your finger and the button is active it will dispatch the
click event – even if your finger is not strictly on the button.
The solution to this problem is actually quite simple. We need to measure the dimensions and determine the location of the button on the screen. Using this information we can calculate the location of the 100 pixel radius. The
touchmove event listener checks if the location of your finger is within the radius and will remove the class if it isn’t. If it is in the radius again, it will add the class again.
So how can we determine the location of the finger on the screen? If we were using mouse events we could look at the event object passed to the event handler. There are two properties called
clientY which would contain the coordinates of the mouse relative to the page. Touch events work similar, but instead of just a single
clientY property we have separate properties for every finger. The event object contains an array called
targetTouches which holds all of those properties. If we assume that we are just working with a single finger we only have to look at the first element of the
targetTouches array and compare its
clientY to the location of the 100 pixel radius.
Just like the previous version of the prototype we want to dispatch our own
click event. And just like before we can simply look at the class of the button to see if we need to dispatch our
click event. If the class is set to active, our finger is within the 100 pixel radius and we need to dispatch the event. If the class is not set, we can just ignore the event.
Use an iPhone or iPod touch to see how the third prototype works.
How to make your icon glow?
As discussed earlier in this article, the iPhone has different types of native buttons and the example above is just one. Some native applications will also show clickable icons instead of actual buttons. If you hold down your finger on such an icon it will not show an alternative state. Instead it will show a white glow around the icon. If you move your finger away from the icon, the glow will disappear. Move your finger back on the icon it will appear again.
So how do we make an icon glow? The glow is little more than a simple radial gradient. The inner part of the gradient is plain white, the outer part is transparent. Using the
canvas element it is almost trivial to create such a gradient and position it on top of the icon.
That is exactly what we need to do. As soon as we receive the
touchstart event we create our canvas and draw a gradient. Using the same logic we already wrote for regular buttons we determine if our finger is within the 100 pixel radius. If it is we simply show the gradient, if is not, we hide the gradient. That is basically all there is to it.
In my example I used the CSS opacity property for a transition. By default the opacity is set to 1, which means it is completely visible. If I hide the gradient I set it to 0 which should make the gradient completely transparent. Without transitions this would be immediate. One moment you see the gradient, the next it is invisible. However, if I add a transition it will slowly fade out and I can specify the exact number of milliseconds that that transition should last.
Use an iPhone or iPod touch to see how the final prototype works.
The result is also available as a zip file and contains the ActiveOnTouch and GlowOnTouch classes, an example and all four prototypes. These classes are based on the Prototype Lite library which is also included in the zip file. Finally, I want to thank Matteo Spinelli from cubiq.org whose articles inspired the code I wrote for this article.
More about iPhone webapp development
This is the third part of an ongoing series of articles called iPhone Webapps 101. The other installments are: