I have been working on the mobile web version of Pico lately and I have quickly stumbled upon many rendering quirks between different versions of Android and iOS. I will show some examples of these and offer some suggestions for workarounds.
I am testing of four devices which should cover a large portion of the smartphone install base, though there are many others that aren’t addressed here.
Android 2.3.6 (Nexus One – Default browser)
Android 4.0 (HTC One S – Default browser, not chrome)
iOS4.2 (iPhone 3G)
iOS5.1 (iPhone 4)
Fixed Position
If you are making an app with a “native feel”, fixed position elements are essential. Topbars and menu buttons are pretty standard in all native apps, and fixed position elements are the most direct way to accomplish that. Well.. too bad they don’t really work that well.
First off, fixed position doesn’t work at all in iOS4. The elements are rendered as static. On Android 2, they work, but you have to have a to set the scale value in the meta tag. Example: Doesn’t work in Android 2 (source), Works in Android 2 (source)
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
Secondly, on iOS5, tap events on fixed position elements don’t work if the page is scrolled. Click events work however, so just use those. Example (source). Stackoverflow (none of those solutions worked for me).
Also on iOS5, if the window is scrolled by anything other than the user (javascript scrollTo, refreshing the page when it is already scrolled, etc.). Fixed position elements become unclickable. What is apparently happening is the click area is being scrolled even though they are being drawn in the right place. The point is, they won’t work until the user scrolls manually, then everything snaps into place. I also had limited success “rerendering” the fixed position elements after the scroll event. i.e. removing them from the DOM and reinserting them (or in Backbone.js, calling render()). Example (source). In depth example.
Lastly, there is a rendering bug in iOS which causes fixed elements to be drawn in the wrong location some times. In particular when dynamic content causes the page to scroll or when the keyboard pops up. I unfortunately wasn’t able to recreate this in jsfiddle, but it does happen, I swear. If the user scrolls the page, everything pops into place.
Transforms
Transforms are great. You can apply all sorts of cool effects to any element on your page. But, more importantly, even for simple effects, they are much faster to animate than normal css transitions because they are accelerated by hardware. 2d transforms work everywhere! 3d transforms have more limited support.
You can easily achieve the same animation effects (and a lot more) with transforms versus conventional CSS animations. (e.g.left:100px ===> -webkit-tranformation: translate3d (100px, 0, 0) ). See the difference here (source). You will only see the difference on mobile browsers, and it is subtle with these simple elements; but if you are animating things with a lot of CSS, it will be slower without using transforms.
Note that if you are transforming an element that contains a fixed element, the fixed element will become static (though it will be transformed). You need to transform the fixed and non-fixed elements separately. This is not actually a mobile bug, it works the same way on desktops, but is still a little counterintuitive. On Android2, it doesn’t work at all, so you’ll need to fall back to non-accelerated animations. Examples: broken (source), working (source), Android2 version (source).
Another thing I noticed is that on Android, when I enabled 3d acceleration on an element dynamically, it caused the element to blink on screen. That’s not a very common thing to do, but can be annoying.
Tap events
Tap events (touch start, touch move, and touch end) can be used to create cool low-level interactions like drag and drop. However, their most common use case is to get rid of the delay from a click event. On both Android and iOS, there is a 300ms delay after you click (presumably to make sure you’re not double clicking). This can create a laggy user experience. You can instead use touch end events (on mobile only) to perform actions. See the difference here.
Don’t forget that tap events don’t work properly in fixed elements in iOS (mentioned above)
On Android, tap events can improperly cause a “click” action on elements below them. This seems to only happen if the element moves when you tap it. You can either have a filter element between the two that traps clicks, or just use a “click” event on the top element. Example (source).
Scrolling Divs
Scrolling divs have many uses. Sometimes you want different elements on the page to scroll independently. They can also be used to implement fixed topbars without using ‘positon:fixed’ elements.
Unfortunately, they don’t work on Android 2 or iOS4 (however, iScroll uses transformations to fake it).
On Android 4, javascript scrolling (i.e. element.scrollTop=x) does not work. Example (source). But really, if you want to do this, you should just use iScroll.
Conclusion
These are all quirks/bugs I found in about 2 weeks of mobile development. I am sure there are many more out there. This is very disappointing. I feel like I am back in 1999 where no browser is standards compliant for even the most basic things and my code is riddled with IF statements checking the user agent. Fundamental features like scrolling divs and fixed position elements seem to have poor support, while ironically, more modern features like animations and supported fairly well. There are many third party libraries that try to absorb these eccentricities such as jQuery Mobile, Sencha, and iScroll. These are all widely used, but the first two are fairly heavy-weight and impose many additional design decisions beyond abstracting layout differences. I’d love to hear about other peoples experiences with mobile web development and the solutions they have come up with.