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.

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 144×144, 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, 320×460 for iPhone, 640×920 (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?