Moving Pictures via Javascript

Ok, I had promised to let anyone know that happened to be reading before Christmas, that I would talk about the collage script that I am using on the Christoper Ward Photography site. I am using the lytebox script when you click on an image, but the collage image that moves the pictures around the screen is all mine.

You can get the html source from that page. Here is the Javascript and the CSS files that are used. You can use this code, but please leave the Javascript comment header with my name on it.

/*
	collage javascript
	
	You are free to use this script provided you include this header

	Created by Chris Ward
	veggie2u@cyberward.net
	http://www.cyberward.net/blog/
*/

function Collage(pName) {
	this.name = pName;
	this.state = "closed";
	Collage.list[Collage.quantity] = this;
	Collage.quantity += 1;
}

Collage.prototype.open = function() {
	Collage.closeAll();
	for( i=0; i < this.endPoints.length; i++ ) {
		$image = this.name+'img'+(i+1);
		new Effect.Move($($image), { x:this.endPoints[i][0], y:this.endPoints[i][1], mode: 'absolute' });
	}
	this.state = "open";
}

Collage.prototype.close = function() {
	for( i=0; i < this.endPoints.length; i++ ) {
		$image = this.name+'img'+(i+1);
		new Effect.Move($($image), { x:this.startPoint[0], y:this.startPoint[1], mode: 'absolute' });
	}
	this.state = "closed";
}

Collage.prototype.swap = function() {
	if( this.state == "closed" ) {
		this.open();
	} else {
		this.close();
	}
}

Collage.closeAll = function() {
	for( i=0; i < Collage.quantity; i++ ) {
		var $aCollage = Collage.list[i];
		if( $aCollage.state == "open" ) {
			$aCollage.close();
		}
	}
}

function doOnLoad() {
	$$('img.collageImg').each(function(s) {
		s.show();
	});
	collage1.swap();
}

Collage.quantity = 0;
Collage.list = new Array();

var collage1 = new Collage("g1");
collage1.endPoints = [ [280, 80], [300, 180], [400,100], [580,120], [500,200], [280,400], [340,320], [540,290], [480,380], [620, 440] ];
collage1.startPoint = [4, 60];

var collage2 = new Collage("g2");
collage2.endPoints = [ [220, 100], [360, 120], [280,220], [300,390], [380,300], [580,100], [500,180], [470,420], [560,330], [660, 420] ];
collage2.startPoint = [4, 180];

var collage3 = new Collage("g3");
collage3.endPoints = [ [280, 80], [240, 230], [400,100], [460,180], [280,400], [340,320], [540,290], [600, 400], [580, 130], [480,380] ];
collage3.startPoint = [4, 300];

var collage4 = new Collage("g4");
collage4.endPoints = [ [280, 80], [300, 230], [400,100], [540,120], [580,200], [340,400], [280,320], [480,290], [540,380], [700, 110], [650, 350] ];
collage4.startPoint = [4, 420];
body {
	background: black;
	margin:0;
	padding:0;
	height:100%;
}

#albums {
	position:absolute;
	top:10px;
	left:20px;
}

#about {
	position:absolute;
	top:530px;
	left:10px;
}

#about img {
	border:0;
}
#about a {
	text-decoration:none;
}

#title {
	position:absolute;
	top:10px;
	left:220px;
}

#collages {
	background-image:url("./images/albums_background.png");
	position:absolute;
	left:0;
	top:0;
	width: 180px;
	height: 100%;
	margin-top:0px;
	margin-bottom:0px;
}
#collages img {
	margin: 10px;
}

#content {
	margin-left:180px;
	color:white;
}
#content img {
	border:0;
	display:block;
	margin-left:auto;
	margin-right:auto;
}

.collageImg {
	border: 1px solid white;
}

.collageThumb {
	opacity:0.4;filter:alpha(opacity=40);
	border: 1px solid white;
}

#g1img1, #g1img2, #g1img3, #g1img4, #g1img5, #g1img6, #g1img7, #g1img8, #g1img9, #g1img10, #g1img11 {
	position: absolute; top: 60px; left: 4px;
}
#g1img11 {
	opacity:0.4;filter:alpha(opacity=40);
}

#g2img1, #g2img2, #g2img3, #g2img4, #g2img5, #g2img6, #g2img7, #g2img8, #g2img9, #g2img10, #g2img11 {
		position: absolute; top: 180px; left: 4px;
}
#g2img11 {
		opacity:0.4;filter:alpha(opacity=40);
}

#g3img1, #g3img2, #g3img3, #g3img4, #g3img5, #g3img6, #g3img7, #g3img8, #g3img9, #g3img10, #g3img11 {
		position: absolute; top: 300px; left: 4px;
}
#g3img11 {
		opacity:0.4;filter:alpha(opacity=40);
}

#g4img1, #g4img2, #g4img3, #g4img4, #g4img5, #g4img6, #g4img7, #g4img8, #g4img9, #g4img10, #g4img11, #g4img12 {
		position: absolute; top: 420px; left: 4px;
}
#g4img12 {
		opacity:0.4;filter:alpha(opacity=40);
}

The way that it is implemented is pretty simple. In order to make this work, I relied on a naming convention. Lets look at one image:


The id of the image is set to g1 for 'gallery 1', img1 for 'image 1'. If you look at the html, I had to add this logical id to every image. This is a very tedious process. There is also the code for lytebox, namely the rel="lytebox[one]" to launch the lytebox script when the image is clicked, and link all the 'one' galleries together. The other thing on this line is the style="display:none;" part. This bugs me, but it appears to be a css limitation I can't get around. Because all the images are stacked, they all appear in turn as they are downloaded without this. If I put this style in the css, then javascript can't turn it back on! It has to be an inline style. I would love to hear a solution for this.

Then we have one more line from the html file to look at.


This line is the last line in each gallery, and is the last image repeated. This is the one that will not move, and forms the thumbnail for the gallery. Via css, the transparency is lowered when the collage is out. This image contains the onClick call to move the images.

Now onto the javascript. Look at this bit of code that defines a collage:

var collage1 = new Collage("g1");
collage1.endPoints = [ [280, 80], [300, 180], [400,100], [580,120], [500,200], [280,400], [340,320], [540,290], [480,380], [620, 440] ];
collage1.startPoint = [4, 60];

This defines gallery 1 ("g1"). All those numbers are the end points for where each image will be when the collage is expanded. Again, this is a pain, and it can be confusing which image you are adjusting when changing the numbers.

So lets look at the open function:

Collage.prototype.open = function() {
	Collage.closeAll();
	for( i=0; i < this.endPoints.length; i++ ) {
		$image = this.name+'img'+(i+1);
		new Effect.Move($($image), { x:this.endPoints[i][0], y:this.endPoints[i][1], mode: 'absolute' });
	}
	this.state = "open";
}

This is pretty simple. Once I found the Effect.Move() function of scriptaculous, this simply loops over the images in the gallery, and tells the Move function to move the images from where it is (stacked with the others), to the endpoint. The close is similar. It moves the images from where they are (the endpoints) to the start point that was defined in the gallery definition.

The rest of the script is pretty simple. The other methods show all the images (doOnLoad), or set up the swap.

What could be improved? A lot. I don't like defining the galleries and the images through the id field. The user of the collage js has to have intimate knowledge of how the script works in order to set it up. Not only that, but you need matching elements defined in CSS correctly. Having the end point definitions in the js is a pain too. Would would be better, is to come up with a way to have the javascript scan the html and look for images to use, much like how lytebox does it via the rel attribute. Well, guess what? That is exactly what I am working on!