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?

2 thoughts on “JS Parallax Effect

  1. Ive found that if i use javascript ievnrtal based animation with the transform property I have no problems, even when I use functions to parse the transform property and augment it with a new variable. This even works on the iphone smoothly so long as you do not trigger any dom manipulation other than transform or opacity (the performance enhanced properties).

    • Thanks for your feedback! I’ve just started looking into CSS transformations, but it looks to remove quite a lot of code, and be generally smoother as well.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>