February 20th, 2009
One of my colleagues, Shon Taylor of Catapult Imaging Studios, has recently joined forces with Jason Dauphinee, Rod Grainger and Hugh Ruthven (all formerly of DDB Canada) to found a new agency called Bone Creative. They have asked me to join their development team and, as I generally think it a good idea to surround oneself with talented people, I have accepted.
I'm just about finished the Bone Creative website and thought I might share one of my discarded concepts. We were looking for a cool way to display our collective portfolio pieces and the idea of a 'carousel' type display was tabled. This was one of the initial prototypes I came up with.
Hover your mouse pointer over the image below to change the carousels direction:
Although the carousel makes for a nice effect, it wasn't exactly what everyone wanted. I did end up doing something quite similar however but, visually, it looks more like a parabola put on it's side than a carousel. To take a look, check out Bone Creative's portfolio.
Anyway, I thought that I might share my code since it's a fun idea to play with and could easily be elaborated upon. For starters, this is the main or 'document' class:
package
{
import ImageObject;
import flash.display.*;
import flash.events.*;
import flash.net.*;
public class Carousel extends Sprite
{
private var numOfItems:Number;
private var radiusX:Number = 200;
private var radiusY:Number = 75;
private var centerX:Number = stage.stageWidth / 2 - 25;
private var centerY:Number = stage.stageHeight / 2 - 35;
private var scale:Number = 0.66;
private var speed:Number = 0.005;
private var angle:Number;
private var xmlData:XML;
private var xmlLength:uint;
private var xmlList:XMLList;
private var containerArray = new Array();
private var imageArray = new Array();
private var imageLoader:Loader;
public function Carousel():void
{
loadXML();
}
private function loadXML():void
{
// Get XML data
var loader:URLLoader = new URLLoader();
loader.load(new URLRequest("carousel.xml"));
loader.addEventListener(Event.COMPLETE,addImage);
}
private function addImage(e:Event):void
{
xmlData = new XML(e.target.data);
xmlList = new XMLList();
xmlList = xmlData.portfolio.img.@src
xmlLength = xmlData.portfolio.children().length();
// Cycle through data - add image to stage
for (var i:uint = 0; i < xmlLength; i++) {
angle = i * ((Math.PI * 2) / xmlLength);
// Create new object
imageArray[i] = new ImageObject(xmlList[i], angle);
// Load 'em up
imageLoader = new Loader();
imageLoader.load(new URLRequest('images/' + imageArray[i].img));
// Move 'em out
addChild(imageArray[i]);
imageLoader.scaleX = imageLoader.scaleY = scale;
imageArray[i].addChild(imageLoader);
// Reposition everyframe
imageArray[i].addEventListener(Event.ENTER_FRAME, positionImage);
// Add mouse event listener to adjust speed
stage.addEventListener(MouseEvent.MOUSE_MOVE, adjustSpeed);
}
}
private function positionImage(e:Event):void
{
for (var i:uint = 0; i < xmlLength; i++ ) {
// A little trig to get the x and y coordinates
imageArray[i].x = Math.cos(imageArray[i].ang) * radiusX + centerX;
imageArray[i].y = Math.sin(imageArray[i].ang) * radiusY + centerY;
// Scaling to create 'depth'
var imageScale:Number = imageArray[i].y / (centerY + radiusY);
imageArray[i].scaleX = imageArray[i].scaleY = imageScale;
// Change the angle
imageArray[i].ang += speed;
}
swapDepth();
}
private function swapDepth():void
{
// Sort images
imageArray.sortOn("scaleX");
// Set depth
for(var i:uint = 0; i < xmlLength; i++)
{
setChildIndex(imageArray[i], i);
}
}
private function adjustSpeed(e:MouseEvent):void
{
speed = (mouseX - centerX) / 100000;
}
}
}
And the class below, well, that's what pulls it all together - it took me a while to figure this part out. You see, in order to create the illusion of movement, you essentially 'redraw' the object over and over in different positions. The objects position on the ellipse is calculated through the use of some basic trig as you can see in the class above. The key is the objects angle in relation to the ellipses radius.
Now, in order to 'track' the angle of the object from one frame to the next, one has to have a means by which to 'save state' if you will. In order to do this, you need to create an object and an instance of that object that contains both the 'name' of the image as well as the coresponding angle - hence the class below:
package
{
import flash.display.*;
public class ImageObject extends Sprite
{
public var img:String;
public var ang:Number;
private var imgLoader:Loader = new Loader();
// Create object with image and angle properties
public function ImageObject(i:String,a:Number)
{
img = i;
ang = a;
}
}
}
There may be another way to do this through the use of static arrays or something but this solution seems to be the most appropriate and makes the most sense from an OO perspective. If you would like to download the complete example you can do so here. Let me know if you come up with anything cool.
Have fun!