subbu.org

Optimizing for iPhone

without comments

Optimizing this blog for easy reading on an iPhone turned out to involve some hacking. It did not take much time to implement, but within the constraints of static HTML pages, I had to combine a couple of tricks to make, firstly, the pages more readable on the iPhone screen, and secondly, to make the pages load faster by cutting down the number of resources downloaded over the poor AT&T Edge network. The solution required using a combination of the user-agent string and CSS3.

My first problem was to provide an alternative stylesheet with the sidebar suppressed and make the fonts a bit bigger so that the content can be read without having to zoom differently for different parts of the page. The appropriate CSS media type for mobile devices is handheld. However, since the Safari browser on the iPhone has more features than a typical handheld device, providing a single stylesheet for both the iPhone and other handheld devices is not a good idea. Apple recommends using the CSS3 media query based on the maximum device width, which is 480px for the iPhone screen.

Among other things, CSS3 adds the notion of media queries. Per CSS3, "a media query consists of a media type and one or more expressions to limit the scope of style sheets". For the Safari browser on the iPhone, this translates to the following media query

<link media="only screen and (max-device-width: 480px)"
href="iphone.css" type="text/css" rel="stylesheet" ></link>

This solution works for the current iPhones, but it will break if and when Apple releases an iPhone with a higher screen resolution. That is my first hack.

The second issue was to stop loading some JavaScript related to those parts of the page that are hidden via the iPhone specific style sheet. All I had to do was stop executing the JavaScript based on the user-agent string.

if(navigator.appVersion.indexOf("iPhone") == -1) {
...
}

The next was to stop loading some JavaScript. On this blog, I am currently using parts of YUI which involved loading six scripts from Yahoo’s servers like this

<script type="text/javascript" src="http://yui.yahooapis.com/2.3.0/build/yahoo/yahoo-min.js"></script>
...

Since I already trimmed down the page to not require YUI on the page, I needed a mechanism to avoid loading these scripts (approximately 90k) altogether on iPhones. Placing the script elements towards the end of each page isn’t exactly the solution I am looking for since it still involves network traffic. One of the two most common solutions for this problem is to conditionally add a script element to the page using JavaScript, as in:

if(navigator.appVersion.indexOf("iPhone") == -1) {
var scriptElem = document.createElement("script");
scriptElem.src = "http://yui.yahooapis.com/2.3.0/build/yahoo/yahoo-min.js";
scriptElem.type = "text/javascript";
document.getElementsByTagName('head')[0].appendChild(scriptElem);
// Repeat the same for other scripts
}

This will work consistently across all browsers provided each script loaded in this manner is completely independent of other scripts. This is because, browsers load such scripts asynchronously, and secondly because there is no cross-browser mechanism to notify that the script has been loaded. As documented at Ajax Patterns, it is possible to build additional checks to detect when a given script is loaded, but I needed a simpler solution than that.

The second most common solution is to use XMLHttpRequest to load and evaluate each script, as in

if(navigator.appVersion.indexOf("iPhone") == -1) {
var req = new XMLHttpRequest();
req.open("GET", "http://yui.yahooapis.com/2.3.0/build/yahoo/yahoo-min.js", false);
req.onreadystatechange() {
if(req.readyState == 4 && req.status == 200) {
eval(req.responseText);
// load the next script recursively in the same manner
}
}
req.send(null);
}

This would work for this use case, but I was in no mood to write so much code to load scripts asynchronously or use some other JavaScript framework for that. All I needed was a compact solution that could be implemented quickly.

A much simpler solution is to create another HTML page containing all my script tags, and load that page via an iframe added conditionally to each page.

if(navigator.appVersion.indexOf("iPhone") == -1) {
ifr = document.createElement("iframe");
ifr.src = "/scripts/scripts.html";
ifr.width = "0";
ifr.height = "0";
document.getElementsByTagName('body')[0].appendChild(ifr);
}

with the scripts.html containing the following:

<html>
<head>
<script type="text/javascript" src="http://yui.yahooapis.com/2.3.0/build/yahoo/yahoo-min.js">
...
</head>
</html>

Presto! That works.

A decent tool for testing this is the iPhoney app on Mac. While it provides a near-iPhone look and feel, and uses the right user-agent string to mimic an iPhone behavior, it does not yet report the correct max-device-width for CSS3 media queries to work. While the technique I used works on iPhones, it does not work completely on iPhoney.

  • Digg
  • del.icio.us
  • Google

December 5th, 2007 at 9:28 am

RSS feed | Trackback URI

Comments »

No comments yet.

Name (required)
E-mail (required - never shown publicly)
URI
Your Comment (smaller size | larger size)
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.

Trackback responses to this post