David Calhoun is a frontend engineer working for Yahoo! in Sunnyvale, and until recently was working on the Mobile team. He has a passion for all things frontend in general. He maintains a developer blog at davidbcalhoun.com and can be reached through Twitter (@franksvalli).
For years we’ve been hearing many good tips about web optimization on desktop browsers, and we’ve seen how simple changes (such as moving scripts to the bottom of a page) can have a dramatic effect on performance, even on high speed broadband connections.
Likewise, we’ve seen many good tips on JavaScript optimization, mostly aimed at desktop users with CPUs running in the 2GHz+ range.
Now with the growing popularity of the mobile web, suddenly these tips have become even more crucial to get right. On mobile, server latency and connection speed become even more of a concern, even for users running on relatively fast 3G connections. Likewise, with some of the fastest mobile processors only running at 1GHz (many Android phones such as the Nexus One), JavaScript performance has become a greater concern.
In short, mobile carries not only the expected baggage of developing for a small screen, but also developing for high latency slow connections and slower processors.
Therefore the goal here is get you acquainted with some techniques for optimizing on mobile, particularly for higher-end smart phones or devices running iOS, Android, and even webOS.
Optimizing images based on screen size
Not all screens are created equal. There’s now a wide gamut of screen sizes and resolutions floating around out there, and you have to be prepared to deliver to them!
If you develop sites with only one set of images, this means everyone gets those images regardless of screen size. Why should a mobile user have to suffer with downloading an image developed for a desktop site, which will only be downscaled by the CSS?
Fortunately, the latest browsers on these devices have CSS media queries, which allow developers to target browsers according to a variety of factors, including screen width. Here’s some sample CSS that delivers one of two versions of an image, depending on the screen size:
/* Screens bigger than 480px */ @media only screen and (min-device-width: 481px) { #header { background-image: url(header-full.png); } } /* Screens smaller than 480px */ @media only screen and (max-device-width: 480px) { #header { background-image: url(header-small.png); } }
The above CSS serves a larger file (header-full.jpg) to browsers running on larger screens, and an optimized smaller file (header-small.jpg) to browsers on smaller screens.
We can also go the other direction. Newer phones such as the iPhone 4 have high-resolution screens, leaving images developer for under 100 dpi (dots per inch) screens looking comparably fuzzy. In this case we might actually want to make the user download a larger image to improve the visual experience at the cost of performance.
We can use media queries to serve higher DPI content (~300 DPI) to these devices while providing fallback content for lower resolutions:
/* High dpi */ @media only screen and (min-resolution: 300dpi), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min--moz-device-pixel-ratio: 1.5) { #header { background-image: url(header-300dpi.png); } } /* Low dpi */ @media only screen and (max-resolution: 299dpi), only screen and (-webkit-max-device-pixel-ratio: 1.5), only screen and (max--moz-device-pixel-ratio: 1.5) { #header { background-image: url(header-72dpi.png); } }
Just in case you’re wondering, the screen resolution information is also available in JavaScript via the window.devicepixelratio object.
Optimizing by connection speed
But wait, there’s more! Android 2.2 recently introduced the navigator.connection object, allowing developers to determine which network a device is running on. This turns out to be useful in making the most of the connection available, and possibly serving higher quality content in cases when a high speed connection is available.
Here’s a peek at the navigator.connection
object on a device running on a 3G connection:
navigator = { connection: { "type": "4", "UNKNOWN": "0", "ETHERNET": "1", "WIFI": "2", "CELL_2G": "3", "CELL_3G": "4" } };
With a simple script we can detect the connection and add this information as a CSS class on the HTML element:
// Initialize variables var connection, connectionSpeed, htmlNode, htmlClass; // Create a custom object fallback if navigator.connection isn't available connection = navigator.connection || {'type':'0'}; // Set connectionSpeed switch(connection.type) { case connection.CELL_3G: // 3G connectionSpeed = 'mediumbandwidth'; break; case connection.CELL_2G: // 2G connectionSpeed = 'lowbandwidth'; break; default: // WIFI, ETHERNET, UNKNOWN connectionSpeed = 'highbandwidth'; } // set the connection speed on the html element, i.e. <html class="lowbandwidth"> htmlNode = document.body.parentNode; htmlClass = htmlNode.getAttribute('class') || ''; htmlNode.setAttribute('class', htmlClass + ' ' + connectionSpeed);
Now we can serve optimized images for each connection with the following CSS:
.highbandwidth .logo { background-image:url('logo-high.jpg'); } .mediumbandwidth .logo { background-image:url('logo-medium.jpg'); } .lowbandwidth .logo { background-image:url('logo-low.jpg'); }
Reduce HTTP requests
This is the same old familiar story – reducing server roundtrips is the major method to speed up your site. And this is even more apparent on mobile networks. You’re likely familiar with a few of these tips already:
- use CSS3 instead of old image-based solutions (border-radius, text-shadow, background linear/radial gradients, box-reflect)
- base64 encode images in CSS as well as HTML
- avoid redirects (unfortunately very common with mobile, for instance yahoo.com on an iPhone redirects to m.yahoo.com)
- cache ajax data
Here’s some new tips you may not have heard of:
- use Emoji pictograms instead of images (see the list) (iOS 2.2+, other Japanese phones)
- move common CSS and JS into external files (creates more HTTP requests initially, but takes advantage of caching and reduces the payload of subsequent page requests)
- avoid using cookies, use localStorage instead (cookies get sent as extra baggage with each new HTTP request)
- don’t rely on traditional caching, use new HTML5 caching instead (cache manifest and client-side databases). And check out the studies on smartphone caching in the links below.
Avoid JavaScript timeout-based animations
JavaScript animations are old-school. Newer browsers have support for CSS3 transitions and animations, which are hardware accelerated in iOS and some desktop browsers.
You may wonder how you can tap into these new animations with JavaScript. Fortunately for us developers, there are new events fired for some key events:
- onwebkittransitionend (ontransitionend for Firefox, onotransitionend for Opera)
- onwebkitanimationstart
- onwebkitanimationiteration
- onwebkitanimationend
For browsers that don’t yet support CSS3 animations, you might want to consider making a fallback based on older timeout-based animations, which have a potential to be quite sluggish on a slower processor. In that case, always be sure to test on the device itself! Just remember that a simulator running on your desktop computer definitely won’t suffice, especially when testing performance.
localStorage and sessionStorage
Think of these as Cookies 2.0. These are JavaScript objects that persist until the browser tab is closed (sessionStorage) or across browser sessions (localStorage).
Just a word of caution: these objects may seem to act like regular JavaScript objects, but they have a major shortcoming: they can only store strings. When you try to store objects, it actually stores the string “[object Object]”. Grr! So be sure to store and retrieve objects with the help of JSON.stringify()
and JSON.parse()
:
var user = { firstName: 'Joe', lastName: 'Schmoe', age: 40 } // Store the object localStorage.userInfo = JSON.stringify(user); // Retrieve the object var user = JSON.parse(localStorage.userInfo);
The exact amount of storage space in localStorage/sessionStorage by each browser may vary, but 5mb seems to have been agreed upon to be the minimum available.
Other JavaScript tips
Here’s a few more tips to optimize your JavaScript on mobile. This is by no means complete! There are definitely more techniques to be discovered, but this should help get you started.
- minimize the need for JavaScript in forms by taking advantage of HTML5 (where supported): the input autofocus attribute, input placeholder text, new form validation (not yet completely implemented)
- reduce startup latency: even if you can load your JS fast (over WiFi), it might actually take longer for the slow processor to parse the script! You can delay parsing by loading the JS in a comment block and eval’ing later (a clever idea created by the Gmail Mobile team).
- take advantage of new client-side database technology (indexedDB is starting to become the favored standard, but is not yet available on iOS 4.2 or Android 2.2)
- geolocation: save/cache the user’s last recorded location
- take advantage of WebSockets where available (currently only supported in iOS 4.2+)
Conclusion
Remember that there’s more to mobile than just smaller screen sizes! You’re delivering content to devices on slower connections and slower processors than normal, so that should definitely be taken into account.
It’s bittersweet really: both the connection and processor are slower, but at the same time some of the latest and greatest HTML5/CSS3 is available to help ease the pain.
References and further reading
- W3C’s Mobile Web Application Best Practices
- Slides: Mobile Web High Performance (Maximiliano Firtman, author of Programming the Mobile Web)
- Best Practices for a Faster Web App with HTML5
- Nokia JavaScript Performance Best Practices
- Mobile cache file sizes (Steve Souders)
- Mobile Browser Cache Limits, Revisited (Ryan Grove)
- Gmail for Mobile HTML5 Series: Reducing Startup Latency