New App: Kid Van Gogh

My latest project just hit the App Store: Kid Van Gogh!

Kids can doodle within the app, and then turn those images into their own games that become apps right in the iPhone (or iPad/iPod)! Being a father myself I’ve tried to keep kids as the primary audience. There is minimal text, audio guidance, and no ads (I know it bugs me every time one of them comes to me for help having gotten into some ad). The games they create can even be played offline (like in the car or airplane) They can also take photos and draw on top of them, my son is a fan of adding glasses, a beard, and a crown to his photo subjects.

For the coders among you, this is a great combination of the various things I’ve been working on recently. The drawing is done on an HTML5 canvas using touch events, and the games created are web apps, including icons, splash screens, and media queries.

I appreciate any feedback you have on the app, bugs you’ve found, or really anything at all! What kind of improvements would you like to see?

Canvas Game Level Builder

This was kind of an interesting question. Someone was building a top-down game (ala Zelda) and wanted to supply a multi-dimensional array, with values that represent different ground types, and have that translated into a gameboard. This way, they could modify the array to create new levels:
var map=[[1,1,1,1,1,1,1,1,1,1],[1,3,0,0,0,0,2,4,0,1],[1,1,1,1,1,1,1,1,1,1]];
Here is the basic code I used, it edits the canvas size to match the map contents, then loops through the map, drawing each block:
var blocksize=30;
var can=document.getElementById('can')
if(can){ctx=can.getContext('2d');}
function init(){
can.width=map[0].length*blocksize;
can.height=map.length*blocksize;
for(y=0;y<map.length;y++){
for(x=0;x<map[y].length;x++){
draw(y,x);
}
}
can.addEventListener('click',builder);
}

function draw(y,x){
kind=map[y][x];
switch(kind){
case 0:
ctx.drawImage(floorimg,x*blocksize,y*blocksize);
break;
case 1:
ctx.drawImage(wallimg,x*blocksize,y*blocksize);
break;
case 2:
ctx.drawImage(blockimg,x*blocksize,y*blocksize);
break;
case 3:
ctx.drawImage(playerimg,x*blocksize,y*blocksize);
break;
case 4:
ctx.drawImage(goalimg,x*blocksize,y*blocksize);
break;
}
}

function ExtractNumber(value){
var n = parseInt(value);
return n == null || isNaN(n) ? 0 : n;
}
You'll see it's using images such as playerimg, which I'm leaving out here for brevity, but it's just along the lines of:
var floorimg=new Image();
floorimg.src=some_image_here;
Which would yield:
Sorry, your browser doesn't support HTML5 Canvas, please try another

So that's cool, change the values in the array (perhaps supply an entirely new map variable via AJAX?), new level. But their real request was that they'd be able to turn this into a level editor, so they'd be able to dynamically change the map by clicking. So, we needed to create a process that would 1) determine where the user clicked 2) convert that to a particular block of the canvas 3) redraw that block with the new texture value. You can see in the init() function near the top of this page that we added an event listener that would fire the builder function when the canvas is clicked. That code is shown here:
function builder(e){
if (e == null) {e = window.event;}
x = e.clientX; //where the click was
y = e.clientY;
offsetX = ExtractNumber(can.offsetLeft)-window.pageXOffset;//where the canvas is
offsetY = ExtractNumber(can.offsetTop)-window.pageYOffset;
x_grid=Math.floor((x-offsetX)/blocksize); //which block in the canvas was clicked
y_grid=Math.floor((y-offsetY)/blocksize);
map[y_grid][x_grid]++;
if(map[y_grid][x_grid]>4){map[y_grid][x_grid]=0;}
draw(y_grid,x_grid);
}

The offsetX and offsetY lines are a bit touchy, because offsetLeft and offsetTop are defined relative to the parent element, so if the canvas is within some other div which has position, it needs to be taken into account...there is this post that provides some other code that handles this dynamism. It may or may not be necessary depending on the design of your site.

You can see the map change if you play with it. The next step in that project was to create an AJAX call that would JSON.stringify the map variable, and submit it to a web service, which would append it to a text file (along with a level name)...later, that file could be retrieved in a similar manner, so levels could be loaded, edited, and saved.

The canvas element is quite versatile. I saw this game the other day and I'm hooked. Are you using canvas in any interesting projects?

Onions

Yes, this has nothing to do with code, but is pretty cool (and useful), and won't be the last such post. As I mention in my 'About the Blog' page, I like all sorts of technology, and one that I want to pursue (once I clean out the storage room) is hydroponic gardening (especially lettuce, as I recently learned how much I enjoy grilled lettuce (little olive oil, high heat, quick scorch, finished with lemon juice, parmesan cheese)). Hmm, as evidenced by this paragraph, I also really like parenthetical asides, even the nested variety.

In any case, the gist of it (water based growing and self sufficiency) has been front of mind recently, and then I heard that you can extend the life of green onions by keeping them in some water. Makes sense, easy to do, so I gave it a shot. My wife made dinner the other night and needed a bunch of green onions. As we typically do, we chop the whitest part off, but this time, instead of pitching it, I tossed them in a highball glass with an inch or two of water. Placed it in the west facing window over the sink, nothing special. That was 4pm.

Fast forward to 8pm. Kids in bed, cleaning up the kitchen. I swear I can already see growth (the cut 'plane' is no longer a plane, but rather, there is a 1mm bump in the middle). Wife thinks I'm seeing things (I am really optimistic generally, so she may be correct).

The next morning it's undeniable, there is about 4mm of growth, but I start to question myself...is it just an illusion? Are the outer layers contracting, making it appear that the core is extending? 24 hours after the cut. No, it is not an illusion. 48 hours after the cut, are you kidding me?!

And with that, I give you, the incredible, edible, onion:

I've read that celery, ginger, garlic, bok choy, and a variety of other things can be grown this way as well...but at the same time, I see avocado given as an example, and I'm not ready to commit to growing a whole tree, so I'd probably pass on that one. I am growing a ghost chili plant outside, so I'll probably post about that at some point. I have a few peppers already showing, and a ton of buds and blooms ready to grow.

Do you do any interior vegetable gardening? What interesting tips have you learned?

HTML5 Canvas Sketch

I've been (slowly) working on an app for kids recently, and it's based around a drawing construct. While my first thought was 'this is going to be hard,' it turned out that it's a pretty straightforward space to work in. On day zero I found this dev.opera page about working with a canvas and drawing.

To be fair, that seemed to be the whole goal in a nutshell...did what I wanted, behaved logically, so I could build off of it, etc. But then someone working on a different drawing app (it's live: Dumpling) reached out for some guidance, and together we worked through a variety of sources (this was awhile ago now, but I'll add citation as I recall them) and streamlined it (from both a code, and graphical perspective).

The basic idea is that you take an HTML5 canvas element, and attach some event listeners for when the mouse button is pressed, the mouse is moved, the button is released...

So here was the start of my HTML:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Draw</title>
<style type="text/css">
body {background:#ccc; margin:0; padding:0}
html {margin:0; padding:0;}
#container { position: relative; margin:0; padding:0px; }
#canvas { border: 1px solid #000; position:absolute; width:298px; margin-left:11px; margin-top:5px; z-index:20; }
#under { border: 1px solid #000; position:absolute; width:298px; margin-left:11px; margin-top:5px; z-index:10; }
</style>
</head>

<body onload="listen()">
<div id="container">
<canvas id="canvas" width="298" height="298"></canvas>
<canvas id="under" width="298" height="298"></canvas>
</div>
</body>
You'll see I have two canvas elements atop one another. This is not the case in the dev.opera tutorial, but is a huge improvement on their approach.

Then we set up the listeners:

<script type="text/javascript">
var canvas,under,context,underCtx;
var points=new Array();
var iphone = ((window.navigator.userAgent.match('iPhone'))||(window.navigator.userAgent.match('iPod')))?true:false;
var ipad = (window.navigator.userAgent.match('iPad'))?true:false;
document.body.addEventListener('touchmove',function(event){ event.preventDefault(); });

function listen(){
canvas = document.getElementById('canvas');
under = document.getElementById('under');
if(canvas){
context= canvas.getContext('2d');
context.lineCap = 'round';
context.lineWidth=4;
if(iphone||ipad){
canvas.addEventListener('touchstart', strokeStart);
}
else{
canvas.addEventListener('mousedown', strokeStart);
}
}
if(under){
underCtx=under.getContext('2d');
underCtx.lineCap = 'round';
}
}
Oh, the reason you're not seeing things like an end to the SCRIPT is that I'm walking through a file, theoretically you could grab all of the code samples, concatenate them, and have the final page (but I'll give you that anyway, so don't bother).

Here you see a few things: a canvas element has a context (similar to a physical canvas having pigment on it), and those have attributes like line width, how line ends are handled, and so on. So I set up the different JS variables to match with the HTML elements and their contexts. I also use this:

document.body.addEventListener('touchmove',function(event){ event.preventDefault(); });

So that when something would have moved the body of the page, it doesn't respond (if I'm dragging my finger, I don't want it to move the page, but instead, want it to be recognized as drawing).

Then I test for iOS devices (that's the market my app will initially target, so you'd want to amend this if you're looking at Android), and begin setting up the event listeners. If it's iOS, I use touch events, otherwise, mouse events (assuming it's a browser based user...if this were really going to be distributed through iTunes [via a PhoneGap type wrapper] you could take those conditional elements out since you'd KNOW touch events would be required).

For those of you that aren't familiar with the syntax:

canvas.addEventListener('touchstart', strokeStart);
Means: "for canvas, if the touchstart event occurs, fire the strokeStart function"

So, what's in that function?

function strokeStart(ev) {
ev.preventDefault();
canvas.addEventListener('touchend', strokeEnd);
canvas.addEventListener('touchmove', stroke);
canvas.addEventListener('mousemove', stroke);
canvas.addEventListener('mouseup',   strokeEnd);
var a=new Object();
if (((iphone)||(ipad))&&(ev.touches[0])){ //iPad
a.x = ev.touches[0].clientX;
a.y = ev.touches[0].clientY;
}
else if (ev.layerX || ev.layerX == 0) { // Firefox
a.x = ev.layerX;
a.y = ev.layerY;
}
else if (ev.offsetX || ev.offsetX == 0) { // Opera
a.x = ev.offsetX;
a.y = ev.offsetY;
}
points.push(a);
}
So, I'm adding OTHER listeners (touchmove, touchend) and grabbing the coordinates of where that touch occurred, and then adding it to the points array. Why? You'll see in just a bit.

First let's look at what the other listeners are doing:

function stroke(ev) {
var a=new Object();
if (((iphone)||(ipad))&&(ev.touches[0])){ //iPad
a.x = ev.touches[0].clientX;
a.y = ev.touches[0].clientY;
}
else if (ev.layerX || ev.layerX == 0) { // Firefox
a.x = ev.layerX;
a.y = ev.layerY;
}
else if (ev.offsetX || ev.offsetX == 0) { // Opera
a.x = ev.offsetX;
a.y = ev.offsetY;
}
points.push(a);
context.clearRect(0, 0, canvas.width, canvas.height);
drawCurveStroke(context);
}

function strokeEnd() {
canvas.removeEventListener('touchend', strokeEnd, false);
canvas.removeEventListener('touchmove', stroke, false);
canvas.removeEventListener('mousemove', stroke, false);
canvas.removeEventListener('mouseup',   strokeEnd, false);
context.clearRect(0, 0, canvas.width, canvas.height);
//these three lines make sure the lower canvas has the same settings as the top canvas
underCtx.lineWidth = context.lineWidth;
underCtx.strokeStyle = context.strokeStyle;
underCtx.fillStyle = context.fillStyle;
drawCurveStroke(underCtx);
points = [];
}

So the stroke function (which fires each time the mouse/touch moves) grabs another coordinate, adds it to the points array, clears the context (of the top canvas) and then calls the drawCurveStroke function (with the context as a parameter). strokeEnd (which fires when the mouse button or finger lifts) removes some listeners, clears the context of the top canvas, and then calls the drawCurveStroke (with the underCtx as the parameter).

So what is going on? In the dev.opera example, there is just a single canvas, and each time a new coordinate is added to the drawing, all of of the line segments connecting preceding coordinates are re-stroked, causing a bit of graininess on the edges of the lines. What we're doing instead is continually clearing the context, so that the line we're drawing is only stroked one time (using the values in the points array). And then, when we lift our finger, that line is copied onto the lower canvas, so that the points array is only dealing with the current line being drawn.

Here is the drawCurveStroke function, I believe the core of it was from sketchfemme.com but I really can't find the exact nugget, that was something the other developer dug up:

function  drawCurveStroke(context) {
if (points.length < 1) {
return;
}
if (points.length < 6) {
var point = points[0];
context.beginPath();
context.arc(point.x, point.y,parseInt(context.lineWidth)/2, 0, Math.PI * 2, true);
context.closePath();
context.fill();
return;
}
context.beginPath();
// move to the first point
context.moveTo(points[0].x, points[0].y);
// curve through the rest, stopping at each midpoint
for (i = 1; i < points.length - 2; i ++)
{
var xc = (points[i].x + points[i + 1].x) / 2;
var yc = (points[i].y + points[i + 1].y) / 2;
context.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
}
context.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);
context.stroke();
}
So the function determines how many points exist in the array, and then uses quadraticCurveTo between them if enough exist.

As promised, here is the consolidated page, you'll see that as you draw, the lines have a much more fluid aesthetic, particularly when compared to the rough lines in the dev.opera link (which reminds me of some sort of Apple IIGS paint program). Feel free to visit in your iOS device as well.

I'll leave it to you to add other controls, you'll want those to change the various settings of the upper canvas, which will then be mirrored to the lower on strokeEnd.

Working in Android? What changes are you making to the code to make it work? And, given the success of DrawSomething, what are your thoughts on other drawing apps (live, or concepts that could see the light of day)?

Javascript Slideshow

Today I was asked how, given a bundle of still images, I would turn them into a movie using javascript. I figured there were two primary routes: I could use a canvas and use the .drawImage() method to draw each image to the canvas in turn, or, I could just use a single image and change the src.

So, I decided to go the image route. Along with the animation of the images, the other asks were for a Play/Pause button, a Stop (that would reset to the first image), an adjustable frame rate, and a 'jump to frame' option.

I began putting all of the images in an array, the idea being that I'd have a function that would change the image, and each time, would increment some counter, so that the next time the function fired, we'd get the next frame. I'd also need some on/off variable that the play/pause button would alter. Here was the beginning:

<body onload="launch()">
</body>
<script>
var frames=['frame1.png','frame2.png','frame3.png','frame4.png','frame5.png','frame6.png','frame7.png','frame8.png','frame9.png','frame10.png'];
var playing=true;
var on_frame=0;
var timer;

function launch(){
document.body.innerHTML='<img id="film" src="frames/'+frames[on_frame]+'"/><br/><button id="actionbutton">Play</button><button onclick="stop()">Stop</button><br/>Milliseconds between frames:<input type="text" id="framerate" value="50"/><br/>Go to frame:<input type="text" id="gotoframe"><button onclick="goFrame(document.getElementById(\'gotoframe\').value);">Go</button>';
document.getElementById('actionbutton').addEventListener('click',play);
play();
}

Oh, you'll see I use image and frame interchangeably as I describe things. If you think of the difference between a movie and a slideshow, it's really just how much time lapses between images...for a movie, it's milliseconds, for a slideshow, probably a few seconds.

So then I worked on the function to change the image:

function change(){
document.getElementById('film').src='http://www.robotwoods.com/images/blog/frames/'+frames[on_frame]; //I have my images in a folder named frames
on_frame++;
if(on_frame==frames.length){on_frame=0;}
if(playing){
timer=setTimeout(change,document.getElementById('framerate').value?document.getElementById('framerate').value:50);
}
}

and the controls:

function play(){
document.getElementById('actionbutton').removeEventListener('click',play);
document.getElementById('actionbutton').addEventListener('click',pause);
document.getElementById('actionbutton').innerHTML='Pause';
clearInterval(timer);
playing=true;
change();
}

function pause(){
document.getElementById('actionbutton').removeEventListener('click',pause);
document.getElementById('actionbutton').addEventListener('click',play);
document.getElementById('actionbutton').innerHTML='Play';
playing=false;

}

function stop(){
document.getElementById('actionbutton').removeEventListener('click',pause);
document.getElementById('actionbutton').addEventListener('click',play);
document.getElementById('actionbutton').innerHTML='Play';
playing=false;
on_frame=0;
}

function goFrame(x){
if((x>-1)&&(x<=frames.length)){
on_frame=x;
if(!playing){document.getElementById('film').src='http://www.robotwoods.com/images/blog/frames/'+frames[on_frame];}
}
else{
alert('Out of range');
}
}

And that was that (and here it is)...one can drop in your image names (and adjust the path accordingly), and then set the time you want for rotation [in reality you'd probably find the time you liked and then hardcode that in (and remove the input box)...but I had the sense the requester hadn't seen their images animated, so wouldn't have a value in mind for that at the onset.

From a performance perspective this can't compete with an actual converted movie (and you'll see that it takes a moment to get the images cached before it becomes a fluid cycle, though this doesn't happen when viewed locally), but it is a pretty straightforward way to add some dynamism to a page.

What are some creative examples you've seen of image swapping or non-movie animations?

Conditional Operator

I learn best by doing, so when I'm trying to learn some new area, I usually build some demo for myself. Along the way, I often know what I want to do, but not HOW, so in my research, often look at other people's code (this is particularly true for me as I don't have a computer science background, so I will have the concept, but need some syntax).

So, periodically I'll hit some piece of code that looks really new to me (even if it's old hat to most). Awhile back I found one of these, and thought I'd mention how much I liked it: the conditional operator (also known as a ternary operator).

A typical if statement is something along these lines:
var ipad;
if(navigator.userAgent.match('iPad')){
ipad=true;
else{
ipad=false;
}

If the userAgent contains 'iPad', the ipad variable is true, otherwise false. So, there is 1) the condition to test, 2) what to do if it is one value, 3) and what to do if it is not...three pieces...ternary. So the alternate syntax for this is:

var ipad=navigator.userAgent.match('iPad')?true:false;

You've probably used this approach in an Excel IF statement, where you layout the condition to test, then the behavior if it succeeds, and if it fails. It's not always helpful, maybe there isn't an else, and so you're just saving a couple of characters, or maybe if the condition is true, you have a lot of code...but it is a nice little tool to have in mind.

Another area where it can be helpful is when you have subtle differences, and only want to change a portion of content, and not duplicate the rest. For instance, on an iPhone, the safari tools are along the bottom, but in an iPad, they're at the top. If I wanted to refer to it in my content (for instance, I have a web app that I want the user to install), I could have something like so:

if(ipad){
content='This page is designed as a web application, which means it can be installed to your iPad like any other application. Please press the arrow icon at the top of the screen, and then select Add to Home Screen.';
}
else{
content='This page is designed as a web application, which means it can be installed to your phone like any other application. Please press the arrow icon at the bottom of the screen, and then select Add to Home Screen.';
}

So I have the same content in my code twice, wasting space, and leaving me with multiple areas to edit if I want to make a change. Instead, I could have done the following:

content='This page is designed as a web application, which means it can be installed to your '+(ipad?'iPad':'phone')+' like any other application. Please press the arrow icon at the '+(ipad?'top':'bottom')+' of the screen, and then select Add to Home Screen.';

Isn't that a bit better? This Wikipedia page has syntax examples for a number of languages.

Media Queries

One element of CSS that is becoming more and more important to understand is media queries. The idea is that by evaluating certain traits of the visitors device (screen size, orientation, resolution, etc) you can then customize your CSS accordingly. So in theory, you would build a single site, that would respond intelligently (Responsive Web Design) depending on the visitor, rather than having a 'full' site, a 'mobile' site, and redirects between them.

The general syntax is that within your <style> code, you have:

@media (parameter_to_test:value){ here is the CSS for this particular scenario }

Here is a recent example I made to demonstrate the concept:

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="data:image/png;base64,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"/>
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Media</title>
<style>
html,body{width:100%;height:100%;margin:0;padding:0;background-color:#f00}
@media screen and (max-device-width: 320px) and (orientation:portrait) {
body { background-color:#FFF; }
}
@media screen and (max-device-width: 320px) and (orientation:landscape) {
body { background-color:#000; }
}
</style>
</head>
<body>
<span style="color:#000">Portrait</span><span style="color:#FFF">Landscape</span>
</body>
</html>

Because this was particular inquiry was actual regarding iPhone orientation changes, you'll see I have some of the apple- header data (I discuss this further in the Web App post). I like to include that in mobile projects because I find most benefit from being installed to the Home Screen.

If you take this code and view it in your browser (or click here), you'll get a big red screen because the CSS outside of the media queries has background-color:#f00 but if you view in on an iPhone (which has a width of 320px [see that value in the media queries?]) you'll have a white background in portrait, and a black background in landscape [note those items in the queries as well].

Update: I realize I didn't fully dive in to the different parameters one can evaluate. Tonight I was asked how to determine if a device was a) typical non-tablet device b) high resolution non-table device c) tablet.

For an ideal solution one would also consider high resolution tablets (iPad 3) but the concept is still sound:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Media2</title>
<style>
body, html {margin:0;padding:0;height:100%;width:100%; background-color:#000000;}
@media (max-width: 767px) { body {background-color:#0000FF;} }
@media (max-device-width: 320px) and (-webkit-min-device-pixel-ratio: 2) { body {background-color:#00FF00;} }
@media (min-width: 768px) { body {background-color:#FF0000;} }
</style>
</head>
<body>
</body>
</html>

So the page is just a 100% wide, 100% high block, with background color black (#000000). Then the media queries are applied:
1) If the device is 767 pixels wide, or smaller, the background is made blue (#0000FF)
2) If the device is 320 pixels wide, or smaller, AND has a pixel ratio of 2 (which the retina devices have), the background is made green (#00FF00)
3) If the device is 768 pixels wide, or larger, the background is made red (#FF0000)

Notice the order as well, if I had test 2 be the first line, it would succeed, making the screen green, if that was followed by test 1, it would also succeed, changing the color again, now to blue.

Obviously these CSS changes are trivial, but it illustrates how a single page can adapt to the device on which it's being used (click here to try your device): Media Queries

For the sake of brevity, I try to place all of the style elements that are NOT impacted by media queries (or default values for those that are, such as the red in the first example, or black in the second) outside of the queries. I have seen pages that have nothing outside of the queries, and then just have the full set inside each query, but I try to avoid repetition (you can also load external CSS files using queries, but the syntax is a little different):

<link rel="stylesheet" media="screen and (min-device-width: 800px)" href="example.css" />
You can also replicate some of these behaviors with javascript, for instance, testing window.innerWidth along with window.orientation, then re-writing some CSS values like so:
document.styleSheets[0].cssRules[0].style.backgroundColor='#0000FF";

Here are some examples of responsive web design. According to Google, nearly 80% of it's top ad buying companies have not designed their site for mobile devices. Is your site mobile optimized? What interesting examples of media queries have you seen?

Web Apps

So web apps are pretty awesome. Of course they have some limitations like access to device hardware (camera), and they can take a little extra time to load depending on how they're built, but overall, the time it takes to put together a convincing prototype web app is small and the results can be impressive. And then there are tools to wrap these builds in enough native code that it can really become a native app: PhoneGap, Appcelerator, etc

I've been in a few prototype coding competitions recently, and have found myself adopting web app solutions more and more. I also receive a lot of questions about this at work, and find myself pointing people to the same resources over and over, so I thought a post here might benefit someone else. Please do find a number of helpful links throughout the post.

The gist of it (and I should preface that I'm biased towards iOS, but many of these elements port to Android just fine) is that you want to take a web page and modify it sufficiently so that it mimics a native application experience. This entails things like: removing the browser elements, adding an icon, adding a launch/splash screen, handling orientation changes, and so on.

Let's jump into it...here is some of the header from a typical app I build:

<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="apple-touch-icon" sizes="57x57" href="icon/icon57.png" />
<link rel="apple-touch-icon" sizes="72x72" href="icon/icon72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="icon/icon114.png" />
<link rel="apple-touch-icon" sizes="144x144" href="icon/icon144.png" />
<link rel="apple-touch-startup-image" media="screen and (max-device-width: 320px)" href="images/iphone_splash.png"/>
<link rel="apple-touch-startup-image" media="(max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2)" href="images/iphone_splash_hires.png" />
<link rel="apple-touch-startup-image" href="icon/ipad_landscape.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape)" />
<link rel="apple-touch-startup-image" href="icon/ipad_portrait.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait)" />
<link rel="apple-touch-startup-image" href="icon/ipad_landscape_hires.png" media="screen and (min-device-width: 481px) and (max-device-width: 1536px) and (orientation:landscape) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="icon/ipad_portrait_hires.png" media="screen and (min-device-width: 481px) and (max-device-width: 1536px) and (orientation:portrait) and (-webkit-device-pixel-ratio: 2)" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

Going line by line: "viewport" tells the device how wide the viewable area should be...it's important because an iPhone is 320 pixels wide (or 640 points in a retina display) but if you don't explicitly set the viewport, it acts as if it's 960 pixels wide (so if your content is 320, you're getting a zoomed out version).

The "apple-touch-icon" links are icons for: iphone, ipad, iphone retina, ipad retina. icon sizes

In my sample I made unique icons for each just to highlight that they were different, but really, I'd build one at 144x144, then scale it down and save it accordingly...unless I had some graphical element that fell apart in the smaller icons, then I'd tweak those versions accordingly. Here are some metrics around icon images: HIG

The "apple-touch-startup-image" links are for the splash screen. This is the image that pops up while the app loads. There are some intricacies around this, mostly with iPad. iPhone is straightforward: portrait, 320x460 for iPhone, 640x920 (don't forget the media portion) for iPhone retina...but iPad is a little odd in that they accept a landscape version as well as portrait, but the images it accepts must always be taller than they are wide, so when you're making the landscape images, you need to rotate the CONTENT, not the image. Not a big deal, but a critical distinction to make.

"apple-mobile-web-app-capable" tells the phone that this page can be installed as an application (using the icon and splash screens we've defined)

"apple-mobile-web-app-status-bar-style" can set the status bar style. There are a few options (default, black, black transparent) but I usually omit it if I want the default, or go with black if the style of the app is dark.

The stage is set, but we have some steps to get through before showing real content, so in order to let the user know we're working on it, I like to start my apps with a little loader graphic that is then replaced when the real load is complete:

<body>
<img src="loader_graphic.gif"/>
</body>

So now the framework is there, but there are a few more subtle details before you get to the content. First, in order for anyone to see the icon and startup image, they have to install it. A user does this by tapping the arrow at the bottom of safari (on an iPhone, it's at the top on iPad), and then 'Add to Home Screen.' So you'll want to keep an eye out for users that can do so, and give them some guidance (more or less politely depending on the purpose...I've had builds where if it was not installed, you would only see an animation of how to do so):
installation guidance screen

But often you'll just see a little tooltip box appear near the arrow as a gentle reminder. So how do we determine when do do any of this? We need to know it's an eligible device, and that it's not already installed. For the first portion of that I do the following:

var ipad=navigator.userAgent.match('iPad')?true:false;
var iphone=(navigator.userAgent.match('iPhone')||navigator.userAgent.match('iPod'))?true:false;

So now we know if it's an iPad, or iPhone (I don't distinguish between iPhone and iPod here). But is it installed? By testing the navigator.standalone property we can find out.

if((iphone||ipad)&&(!navigator.standalone)){
	//here is where you would handle install instructions like 
	//popping up an image about how to "Add to Home"
	str='please install me by pressing the arrow button at the ';
	str+=ipad?'top':'bottom';
	str+=' of the screen, then selecting \'Add to Homescreen\'';
	alert(str);		
}

So, we're close, but not quite there because unlike a full computer, your device has an orientation. You'll want to add a listener for orientationchange to the body, which can be as straightforward as:

<body onorientationchange="updateOrientation();">
in conjunction with a function like this:
function updateOrientation(){
	switch (window.orientation) {
	//while some things will be handled by the phone itself,
	//you may need to change some things manually (for instance,
	//you may have two elements stacked on top of each other,
	//and you'd like them to sit side by side in landscape mode
        case 0:
        alert ('Orientation: Portrait');
        break;
        case 90:
	alert ('Orientation: Landscape');
        break;
        case -90:
        alert ('Orientation: Landscape');
        break;
	case 180:
        alert ('Orientation: Portait');
        break;
    }
}

Obviously javascript is one approach, but some things can be handled with CSS Media Queries, so that's a powerful tool to understand as well.

One detail about the various versions of iOS, is that CSS' position:fixed isn't supported before 5.0, so if I used that (and I do), I want to test for that as well:

if(iphone||ipad) { 
	if(/OS [2-4]_\d(_\d)? like Mac OS X/i.test(navigator.userAgent)) {  
        	alert('This is designed for iOS 5 and above, please update your device for proper functionality'); 
   	} else if(/CPU like Mac OS X/i.test(navigator.userAgent)) {
        	alert('This is designed for iOS 5 and above, please update your device for proper functionality');
    	} else {
		//iOS5+
	}
}

Okay. So the last piece is user interface. If you're trying to mimic a native app, you'll probably use elements like a navigation bar at the top, or tabs at the bottom, etc. Here is how I set up a starting point:

#nav {
width:100%;
height:40px;
position:fixed;
top:0;
text-align:center;
background-color:#29abe2;
color:#FFF;
font-size:150%;
padding-top:5px;
border-bottom:thin solid #444444; display:block;
font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
}
#main{ width:100%; margin-top:45px; }
#footer {
width:100%;
height:44px;
position:fixed;
bottom:0;
left:0;
text-align:center;
color:#FFF;
font-size:150%;
margin-top:5px;
border-top:thin solid #444444; 
background:#000000;
}
And when I'm ready to show the app (it's installed, etc), I just:
document.body.innerHTML='<div id="nav"></div><div id="main"></div><div id="footer"></div>';
And then fill in my content to the main div (I have a function for each view that changes the .innerHTML, and that are linked to tabs that I build in the footer).

So if you'd like to give it a try, visit www.robotwoods.com/dev/web_app_demo from your mobile device. It should work in Android too, the way to install there is to bookmark it, then go to your bookmarks, and long press it, it'll then give you some sort of Home Screen option. I also put the files in a zip here

Oh, you'll see in the actual app that the footer has a gradient, I use this site for that: http://www.colorzilla.com/gradient-editor/ I think subtle gradients add a bit of warmth to a UI that may look dull with solid color blocks (see the icons as an example).

And, to minimize the size of your project (which enables faster download), you may want to minify your javascript. I think there are a variety of options for this, I usually just go with: http://jscompress.com/

Lastly, once everything is running as you like it, there's one more way to improve performance (particularly in those applications that do not require a network connection, for instance, calling a web service for data): cache manifest

By crafting a particular file that outlines all of the files used in your project, the device will (with some limitations) store those files locally, so that it will run even when disconnected from the network. And, as the files are local, it loads much faster as well.

I have covered a number of the important areas of a web app, but there is plenty of other areas and approaches to learn about. What other tips and tricks do you use when building web apps?

JS Parallax Effect

So I have this friend that works in film post-production (you've seen his work). We were having dinner awhile back and, as it always does, the conversation turned to tech. We were discussing the recent trend to 3D films, and he explained that no longer are things necessarily filmed in 3D, but rather 3D-ified. His analogy was that the 2D version is a rubber plane, that can be pushed towards or away from the viewer. This causes some distortion, but is much cheaper.

The thing that also stuck with me was that one of the biggest reasons 3D is far from perfect is PARALLAX. This is the effect where when you move, objects far away move much less than those closer to you (hold up your thumb at arms length, aim at something, then notice how much your thumb appears to move relative to that other thing when you tilt your head). Because our heads are always wobbling, we're using these changes to continually build a 3D map of the scene in front of us, but that can't be replicated in a film because it'd require some sort of per-person eyeball tracking, converted back to the correct depth calculations, and beamed to the right individual.

But anyway, someone recently asked about how they could replicate this effect on the web. There is a jQuery tool that does this: here. But I wanted to understand it, so decided to build my own.

I started with a scenario like the thumb model. I put up a black background with white speck (space), and then a blue ball in the foreground (earth).

body {
height:1px;
width:1px;
display:block;
background-image:url('space.png');
background-repeat:repeat;
} 
#earth {
position:absolute; 
top:1px;
left:1px;
width:50px;
height:50px;
display:block;
background-image:url('earth.png');
background-repeat:no-repeat;
}

I wanted to have the body be fairly large, so I grabbed the height and width of the window, doubled it, and set those dimensions to the document body like so (body was my css rule 0):

var w = 2*window.innerWidth;
var h = 2*window.innerHeight;
document.styleSheets[0].cssRules[0].style.height=h+'px';
document.styleSheets[0].cssRules[0].style.width=w+'px';

And, I wanted to place the earth center screen, so I calculated that point, placed the planet, and then scrolled accordingly:

document.styleSheets[0].cssRules[1].style.top=((h/2)-25)+'px';
document.styleSheets[0].cssRules[1].style.left=((w/2)-25)+'px';
window.scrollTo(((w-window.innerWidth)/2)+25,((h-window.innerHeight)/2)+25);

Then I needed to add some handling in response to some action. I started with onscroll, but using the arrow keys it felt jerky, and the y-axis seemed to jump more per key press, so I went with mousemove, it was much more fluid:

var x,y;
var earth=document.getElementById('earth');
earth.factor=2;
window.addEventListener('mousemove',pieces);
So now we just need to define the pieces function:
function pieces(e){
	paral(earth,e);	
	x=e.clientX;
	y=e.clientY;
}

Well wait, now I'm just calling yet another function? Why I did this will be evident soon, but here's the paral function. It determines how far from the last point (x,y) the mouse has moved, and the moves the pieces according to their 'factor' (and in the opposite direction, because if you lent left, your thumb went to the right):

function paral(t,ev){
if(ev.clientX>x){//moving right
	t.style.left=(t.offsetLeft-(ev.clientX-x)*t.factor)+'px';
}
if(ev.clientX<x){//moving left
	t.style.left=(t.offsetLeft+(x-ev.clientX)*t.factor)+'px';
}
if(ev.clientY>y){//moving down
	t.style.top=(t.offsetTop-(ev.clientY-y)*t.factor)+'px';
}
if(ev.clientY<y){//moving up
	t.style.top=(t.offsetTop+(y-ev.clientY)*t.factor)+'px';
}
}

So this will allow me to just add items to the pieces function as necessary, rather than needing to add line after line for each element, and each direction. And...it worked! But it wasn't quite right. Then I realized my error: When I'd made the background image, I had a variety of star sizes (simulating depth) but they were now all moving relative to something else, and doing so in a way that wasn't natural.

So I then sliced the background into a few different layers (each with ONE size of star), gave each it's own factor property, and then added each layer to the pieces function...and I threw in another planet for kicks. Oh, and one little housekeeping note, because I wanted the mouse to be generally oriented with the planets, I held off on adding the event listener until the mouse was over the earth, like so:

earth.onmouseover=function(){window.addEventListener('mousemove',pieces);}
And with that, I give you my version of the parallax effect using javascript (click to play, then move your mouse to the earth): Parallax

Can you think of some interesting implementations for this kind of treatment? Do you have any suggestions for improvements?