This is a list of the dos and don'ts of building a cross platform HTML5 application as learned during the development of the Iphone and Android application Retrollect.
Topics that will be touched on:
- CSS transforms and gotchas
- What to expect when using experimental features (-webkit prefix)
- Optimizing for mobile
- Debugging on mobile
- Images and reflow
- Animations and touch move events
- Writing custom JavaScript API interfaces for getting data into and out of native device APIs
Slides: http://looking-back-at-retrollect.heroku.com/
Retrollect is the free mobile app that lets you assemble the highlights of your experiences and create a visual mash-up of your life!
Retrollect allows you to create slide shows containing 8 pieces of content from your phone, Twitter account, Facebook status updates, Instagram, or your phone's camera in a visually interesting way. Retrollect uses the aesthetics of the vintage View-Master to add a sense of nostalgia to your collections of content.
Retrollect does this using a codebase mostly comprised of JavaScript, HTML, and CSS.
So you are going to develop a mobile application and are weighing your options, there is Objective-c combined with the cocoa APIs for iOS, Java and Android's API system, there are JavaScript to native code compilers like Titanium Appcelerator, and then there is the option to build straight HTML, JavaScript, and CSS using normal web app practices or an embedded web view.
We've all heard a lot about the shortcomings of HTML5 as a replacement for native code. There's the tired argument about HTML5 vs Native Apps and how HTML5 not good enough yet. I tend to agree and it frustrates me that my web apps aren't as fast or responsive as native code would be, but things are moving forward faster than ever. Looking at a lot of the experiments and absolutely amazing things people are pushing with it, I get really inspired. I think what is really needed is an objective look at what could be done with it, what has been done, and what needs to happen to help push the spec and it's implementations across vendors forward.
The question is how do we do this without absolutely loosing our minds?
- You are fairly proficient with HTML, CSS, and JavaScript.
- You want to be able to deploy bug fixes etc without going through an app store
- The stuff in the HTML5 spec like Web Workers, placeholders, Geoloaction, and all that is super sexy to you.
- You absolutely NEED precise native look and feel in your app
- You have extremely complex UI
"But the iphone's screen size is a set size!", you say?
Just make it fluid man, the point of using HTML5 in the mobile context is to not be tied to any platform beyond a browser. Browsers (even embedded web-views) can be different sizes, think iPhone, iPad, most Android handsets have different screen dimensions.
Making your design fluid (grow to fit any width and height) will save you a ton of headaches when putting your app on more than one device.
Mobile webkit HATES, HATES, HATES images. They are extremely slow to render and manipulate.
Also avoid:
-webkit-gradient
: It paints a bitmap on the viewport and is nearly the same as using images as far as the rendering engine is concerned. Making an iPad HTML5 App & making it really fasttext-shadow
,box-shadow
,opacity
, and alpha background colors also behave similar to using images, these should be avoided wherever performance is a concern.
Good:
<div class="list">
<a href="#">Item 1</a>
<a href="#">Item 2</a>
<a href="#">Item 3</a>
</div>
Bad:
<div id="wrapper">
<div id="inner-wrapper">
<ul class="list">
<li class="list-item">
<a href="#">Item 1</a>
</li>
<li class="list-item">
<a href="#">Item 2</a>
</li>
<li class="list-item">
<a href="#">Item 3</a>
</li>
</ul>
</div>
</div>
Fewer elements means less work for the browser, and less confusion for your development team (this topic could be a whole separate presentation on managing complexity in CSS and Markup.)
Using a minimal amount of markup is really important when using css transforms and expecting reasonably responsive animations.
It's not as broken on Mobile Safari but should still be avoided, it really blows up on android. Caused weird errors with text fields on Android. Fixed positioning in Mobile Safari »
Group DOM Queries and manipulation into separate chunks, do all your manipulation at once instead of spread out.
Quote from Stoyan Stefanov on phpied.com:
Anything that changes input information used to construct the rendering tree can cause a repaint or a reflow, for example:
- Adding, removing, updating DOM nodes
- Hiding a DOM node with display: none (reflow and repaint) or visibility: hidden (repaint only, because no geometry changes)
- Moving, animating a DOM node on the page
- Adding a stylesheet, tweaking style properties
- User action such as resizing the window, changing the font size, or (oh, OMG, no!) scrolling
clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth
- When does JavaScript trigger reflows and rendering?
- Don’t Be Trigger Happy; How To Not Trigger Layout
- How (not) to trigger a layout in WebKit
Im looking at you pinned header and footer with a scrolling list view in between. While we were able to implement an acceptable solution to this common native UI pattern it still doesn't feel right, we got most of the way in a short amount of time but spending time on the rest felt counterproductive.
The Retrollect team spent a lot of time trying to force this behavior into our HTML5 app with a bunch of hacks which ended up being a hit to performance or backed us into other problems like circumventing Maobile Safari's rendering optimizations.
Things that are difficult to translate to into pure HTML5 implementations:
- Scrolling
- Swipe to Delete
- Responsive accelerometer based UI
This is crucial and can actually speed up your development even though it will feel slow at first. It is also important to write tests that are more than testing internal javascript APIs between objects. You should be doing BDD here, when you want to make sure when a user taps a thing that something happens you should write a test for that behavior.
good test:
describe('foo.hasContent()', function(){
when('the slots are empty', function(){
it('should NOT have content', function(){
expect(disc.hasContent()).toBe(false);
});
});
when('the slots are NOT empty', function(){
it('should have content', function(){
expect(disc.hasContent()).toBe(true);
});
});
});
bad test:
describe('foo.hasContent()', function(){
when('the slots are empty', function(){
it('should should call .length', function(){
spyOn(disc._hiddenContent, 'length');
expect(disc._hiddenContent.length).toHaveBeenCalled();
});
});
});
You should play it like you are new to the game. What is going on in these mobile browsers if far different than anything I have ever experienced. For example:
- the autofocus attribute on iputs started crashing mobile safari for apparently no reason. It took about 2 hours to find out removing the autofucs attribute from our login form fixed the problem. Why? Who knows...
- When initially launching our app setInnerHTML would not work, it says it did but no html was getting set. We had to keep calling
setInnerHTML
in a timer that checked that the html was actually added. See "Problems with Safari and innerHTML"
Examine the problems and explore its solution like a scientist. Make guesses, apply them, did it work? Nope? Do it again. Applying knowledge from the desktop world led us down many rabbit holes.
Debugging in a mobile browser is painstakingly slow and not accurate, no exceptions etc.
Also don't expect them to behave the same cross platform, things like timeouts and connection interruptions will look different then you are used to.
We had a haunting issue with base64 encoding images and it turned out to be limits on the heap size, take a look at this message board convo.
3D Transforms are hardware accelerated on iOS and have the bugs worked out for the most part, it is ideal the use them on iOS devices. But they don't work very well on Android, you should use normal transforms instead.
I am a huge fan of docco, take a look at these guys:
Without docs:
if (navigator.userAgent.indexOf('Android') != -1) {
// ...
}
With docs:
// Before doing transforms detect which environment we are in,
// Android's 3D Transforms are a bit buggy so use a 2D Transform
// instead.
// [See this StackOverflow issue for details](http://bit.ly/kwsl09)
if (navigator.userAgent.indexOf('Android') != -1) {
// ...
}
That's it, check out Retrollect which was built by Border Stylo.
- Mistakes Made Building Netflix for iPhone
- The Problem with Sencha Touch and PhoneGap
- Dive Into HTML5
- HTML5 for Web Designers
- CSS3 For Web Designers