This tutorial will demonstrate how to create a horizontally scrolling image viewer. It will cover xml parsing, loading and resizing external images, and creating intuitive and responsive scrolling!
Create a Responsive Image Scroller in ActionScript 3.0
Aug 26th in ActionScript, XML by Evan MullinsQuick Introduction
I'm a firm believer in learning by coding and tinkering. So in this tutorial, I'm stating what to do and then showing the equivalent code; please dig into the code, change things and tinker - I think that's one of the best ways to learn! I expect that you have a basic understanding of ActionScript already and I won't explain the fundamentals. I hope the descriptions will be helpful, but if I don't explain something that you aren't quite following, feel free to ask about it in the comments.
Step 1: Set Up XML
We'll want nodes of images included in our xml. Each node will represent an image with attributes. We can add anything we like here, I'll stick to three attributes to each image node. The 'src' of the image (used to load it in), the title and the url we will link to from the scroller. I'll go ahead and wrap them all within an images node (in case I want to include anything else in the xml later, such as customization options - hint).
Each image node will need a source image, a title and a url. The source (src) will be the image that gets loaded into the scroller, the title will be what we call it and the url will be the url that we load from the scroller when users click on the image (it can be a larger image or even a website). I've gotten some images from the envato asset library and resized them to be used as the thumbnails as well. I shrank the thumbnails to fit into a 140px by 105px area, this is not required but will help to optimize the experience since we won't be loading in huge images. You'll see soon that any size image will work because we'll code it to resize any image that loads to fit into the scroller area!
<images> <image src="images/tn/scottwills_underwater4.jpg" title="Jelly 4" url="images/scottwills_underwater4.jpg" /> <image src="images/tn/scottwills_cat.jpg" title="Cat" url="images/scottwills_cat.jpg" /> <image src="images/tn/scottwills_modern_sculpture.jpg" title="Statue" url="images/scottwills_modern_sculpture.jpg" /> <image src="images/tn/madness_arch3.jpg" title="Arch 3" url="images/madness_arch3.jpg" /> <image src="images/tn/scottwills_penguin.jpg" title="Penguin" url="images/scottwills_penguin.jpg" /> <image src="images/tn/scottwills_horse.jpg" title="Jelly" url="images/scottwills_horse.jpg" /> <image src="images/tn/scottwills_modernsculpture2.jpg" title="Statue 2" url="images/scottwills_modernsculpture2.jpg" /> <image src="images/tn/madness_arch1.jpg" title="Arch 1" url="images/madness_arch1.jpg" /> <image src="images/tn/madness_arch2.jpg" title="Arch 2" url="images/madness_arch2.jpg" /> </images>
Step 2: Set Up Flash
OK, now that we have our xml file finished, let's get started in Flash. Open Flash and create a new file (ActionScript 3.0). Adjust the stage size to something around 600 wide and 150 tall, set the background to any color you like (I chose a dark color to help the images stand out a bit more - #000033 if you wanna follow along) and step up the frame rate to 30 frames per second to give us smoother animation. Then let's go ahead and create a new layer calling it "ActionScript". On the first frame in the timeline click to the actions panel and we're ready to continue!
Step 3: Load XML
We need to load the information from the xml file (created in step 1) into our Flash file. It's a pretty simple step, but a foundation which can spring to many other creative applications. In just a few lines of code we initialize a URLLoader, an XML object to hold our data and a string to specify the path to the xml. Then we load the xml through the urlLoader object. That done, we need an Event listener to fire off when the file is fully loaded, we wouldn't want to start messing with the xml data before it finishes loading. As specified in the docs, the complete event passes the loaded content as a the event target data property, so we access the actual xml as e.target.data and assign it to our xml object.
Let's go ahead and double check that we've set that all up correctly and trace the image nodes of our xml to the output panel.
//load xml
var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
var xmlPath:String = "image-scroller.xml";
xmlLoader.load(new URLRequest(xmlPath));
trace("loading xml from: " + xmlPath);
xmlLoader.addEventListener(Event.COMPLETE, LoadXML);
function LoadXML(e:Event):void {
trace("xml loading complete");
xmlData = new XML(e.target.data);
trace(xmlData.image); //we'll see each image xml element listed in the output panel with this xmlList
}
/*
output window
///////////////
loading xml from: image-scroller.xml
xml loading complete
<image src="images/tn/scottwills_underwater4.jpg" title="Jelly 4" url="images/scottwills_underwater4.jpg"/>
<image src="images/tn/scottwills_cat.jpg" title="Cat" url="images/scottwills_cat.jpg"/>
<image src="images/tn/scottwills_modern_sculpture.jpg" title="Statue" url="images/scottwills_modern_sculpture.jpg"/>
<image src="images/tn/madness_arch3.jpg" title="Arch 3" url="images/madness_arch3.jpg"/>
<image src="images/tn/scottwills_penguin.jpg" title="Penguin" url="images/scottwills_penguin.jpg"/>
<image src="images/tn/scottwills_horse.jpg" title="Jelly" url="images/scottwills_horse.jpg"/>
<image src="images/tn/scottwills_modernsculpture2.jpg" title="Statue 2" url="images/scottwills_modernsculpture2.jpg"/>
<image src="images/tn/madness_arch1.jpg" title="Arch 1" url="images/madness_arch1.jpg"/>
<image src="images/tn/madness_arch2.jpg" title="Arch 2" url="images/madness_arch2.jpg"/>
*/
Step 4: Parse XML
Loading the xml is great, but we also have to read it and let our code know what to do with the data. So rather than just tracing out the xmlList we'll send it to another function that will take care of creating our scroller. The buildScroller function will accept an XMLList, so we'll loop through it, creating movie clip objects for each image in the scroller and assigning them properties according to the xml node attributes title, url and src. Later we'll build out this function to include much more so it will actually do what it is named for and build the scroller!
For now let's just trace this info to make sure everything is working according to plan. We can even wrap the content buildScroller function with trace statements so we always know where we are.
//load xml
var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
var xmlPath:String = "image-scroller.xml";
xmlLoader.load(new URLRequest(xmlPath));
trace("loading xml from: " + xmlPath);
xmlLoader.addEventListener(Event.COMPLETE, LoadXML);
function LoadXML(e:Event):void {
trace("xml loading complete");
xmlData = new XML(e.target.data);
buildScroller(xmlData.image); //rather than trace the xmlList, we send it to our buildScroller function
}
//build scroller from xml
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
trace(thisOne.itemNum, thisOne.title, thisOne.link, thisOne.src);
}
trace("termination of build scroller");
}
/*
output
///////
loading xml from: image-scroller.xml
xml loading complete
build Scroller
0 Jelly 4 images/scottwills_underwater4.jpg images/tn/scottwills_underwater4.jpg
1 Cat images/scottwills_cat.jpg images/tn/scottwills_cat.jpg
2 Statue images/scottwills_modern_sculpture.jpg images/tn/scottwills_modern_sculpture.jpg
3 Arch 3 images/madness_arch3.jpg images/tn/madness_arch3.jpg
4 Penguin images/scottwills_penguin.jpg images/tn/scottwills_penguin.jpg
5 Jelly images/scottwills_horse.jpg images/tn/scottwills_horse.jpg
6 Statue 2 images/scottwills_modernsculpture2.jpg images/tn/scottwills_modernsculpture2.jpg
7 Arch 1 images/madness_arch1.jpg images/tn/madness_arch1.jpg
8 Arch 2 images/madness_arch2.jpg images/tn/madness_arch2.jpg
termination of build scroller
*/
Step 5: Build Scroller MovieClip to Contain Each Image
Now that the build scroller function is created and that it creates objects for each node in the xml, let's add them to the stage!
We'll begin by creating a scroller MovieClip. This will be the container for all the objects we want to scroll, so each image node will have a movieclip within this scroller object. Let's go ahead and add it to the stage, then set the y value to 30. We won't yet see anything on the stage when we test, but we're about to add items to the scroller for each image in the xmlList.
var scroller:MovieClip = new MovieClip(); this.addChild(scroller); scroller.y = 30;
Step 6: Adding Items to Scroller
We already have the movie clips created with properties, but we need to create something we can see, so let's create a box area to actually see these scroller items. Create a blackBox sprite and give it a shape with the graphics api. I've decided that I want my standard scroller item to be 140x105px so I'll create the blackBox to fit that area and I'll give it a 1px border on all sides. Move it up and to the left one pixel and give it 142x107 dimensions.
We add the blackbox sprite to the thisOne movieClip and also update the x value of the item. Since each will be 140 wide, I'll add some padding (20) and lay them out horizontally. I'll update the trace statement to output what is going on here.
var scroller:MovieClip = new MovieClip();
this.addChild(scroller);
scroller.y = 30;
//build scroller from xml
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
//outline
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
thisOne.addChild(blackBox);
thisOne.x = (140 + 20) * item;
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
trace(thisOne.itemNum, thisOne.title, " added to scroller");
//add item
scroller.addChild(thisOne);
}
trace("termination of build scroller");
}
/*
output
///////
loading xml from: image-scroller.xml
xml loading complete
build Scroller
0 Jelly 4 added to scroller
1 Cat added to scroller
2 Statue added to scroller
3 Arch 3 added to scroller
4 Penguin added to scroller
5 Jelly added to scroller
6 Statue 2 added to scroller
7 Arch 1 added to scroller
8 Arch 2 added to scroller
termination of build scroller
*/
Step 7: Adding Click Listener to Items
Alright, this isn't much yet because it just looks like a bunch of boxes that don't do anything. Let's add a listener to these boxes though, so that we can tell that they are each unique items in the scroller. Thinking of the final result, we'll add a click listener because we want users to be able to click the item in the scroller and go to the link we put in the xml. Let's make the clip buttonMode true, so that it's perceived as something that is click-able. Then add an event Listener for the MouseEvent.CLICK. For now we'll just have it listen for this click event and fire a function to trace the url we're aiming at.
Through the output we can see that each box has properties sent to it from the xml.
//build scroller from xml
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
//outline
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
thisOne.addChild(blackBox);
thisOne.x = (140 + 20) * item;
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
//create listeners for this item
thisOne.buttonMode = true;
thisOne.addEventListener(MouseEvent.CLICK, clickScrollerItem);
//add item
scroller.addChild(thisOne);
}
trace("termination of build scroller");
}
function clickScrollerItem(e:MouseEvent):void {
trace("clicked item " + e.currentTarget.itemNum + " - visit url: " + e.currentTarget.link);
}
/*
output
//////
loading xml from: image-scroller.xml
xml loading complete
build Scroller
termination of build scroller
clicked item 2 - visit url: images/scottwills_modern_sculpture.jpg
clicked item 0 - visit url: images/scottwills_underwater4.jpg
clicked item 1 - visit url: images/scottwills_cat.jpg
clicked item 3 - visit url: images/madness_arch3.jpg
clicked item 1 - visit url: images/scottwills_cat.jpg
*/
Step 8: Adding More Listeners to Scroller Items
Let's finish off the event listeners! We'll want to listen to each of these items to know when a user clicks, mouses over and mouses out.
//build scroller from xml
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
//outline
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
thisOne.addChild(blackBox);
thisOne.x = (140 + 20) * item;
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
//create listeners for this thumb
thisOne.buttonMode = true;
thisOne.addEventListener(MouseEvent.CLICK, clickScrollerItem);
thisOne.addEventListener(MouseEvent.MOUSE_OVER, overScrollerItem);
thisOne.addEventListener(MouseEvent.MOUSE_OUT, outScrollerItem);
//add item
scroller.addChild(thisOne);
}
trace("termination of build scroller");
}
function clickScrollerItem(e:MouseEvent):void {
trace("clicked item " + e.currentTarget.itemNum + " - visit url: " + e.currentTarget.link);
}
function overScrollerItem(e:MouseEvent):void {
trace("over " + e.currentTarget.title);
}
function outScrollerItem(e:MouseEvent):void {
trace("out " + e.currentTarget.title);
}
/* output
//////////////
loading xml from: image-scroller.xml
xml loading complete
build Scroller
termination of build scroller
over Jelly 4
out Jelly 4
over Cat
out Cat
over Statue
out Statue
over Arch 3
out Arch 3
over Arch 3
clicked item 3 - visit url: images/madness_arch3.jpg
out Arch 3
over Statue
clicked item 2 - visit url: images/scottwills_modern_sculpture.jpg
out Statue
*/
Step 9: Loading and Adding the Images
Now the part I'm sure you've been getting anxious about, let's get the images loaded into the scroller items so we can see what they are! We'll do this in the buildScroller function. Right after the blackbox is created and the properties are assigned for the current item let's also create another sprite to contain the actual image.
We'll use a loader object and a URLRequest object to handle the loading of the image, which is newly standardized with ActionScript 3. We'll pass the src of the image to the urlrequest object, then load the urlrequest with our loader object and finally we'll add the loader to the thisThumb sprite adding that in turn to the thisOne movieClip. Whew.
It's pretty self explanatory - and in just a couple of lines of code we've loaded the src images from our xml into Flash!
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
//outline
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
thisOne.addChild(blackBox);
thisOne.x = (140 + 20) * item;
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
//image container
var thisThumb:Sprite = new Sprite();
//add image
var ldr:Loader = new Loader();
var urlReq:URLRequest = new URLRequest(thisOne.src);
trace("loading thumbnail "+item+" into Scroller: " + thisOne.src);
ldr.load(urlReq);
thisThumb.addChild(ldr);
thisOne.addChild(thisThumb);
//create listeners for this thumb
thisOne.buttonMode = true;
thisOne.addEventListener(MouseEvent.MOUSE_OVER, overScrollerItem);
thisOne.addEventListener(MouseEvent.MOUSE_OUT, outScrollerItem);
thisOne.addEventListener(MouseEvent.CLICK, clickScrollerItem);
//add item
scroller.addChild(thisOne);
}
trace("termination of build scroller");
}
Step 10: Adding Event Listeners to the Image Loading Process
When we load the images, we need to create some event listeners to tell us when the loading is complete. Best practice deal with any errors (in case there is a typo in the path to the image or we don't have access or something). So just before we load the images let's add an event listener for the loader's contentLoaderInfo object for a complete event and also an i/o error. Then we'll need to create the event handler functions.
For the completeHandler let's just trace the image title for now, we'll have to walk up the hierarchy of the event target's parent's parent's title to find it, because of the way we nested our sprite within our scroller item objects (the thisOne Sprite).
trace("loading thumbnail "+item+" into Scroller: " + url);
//assign event listeners for Loader
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
ldr.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
ldr.load(urlReq);
...
function completeHandler(e:Event):void {
trace("thumbnail complete "+e.target.loader.parent.parent.title);
}
function errorHandler(e:IOErrorEvent):void {
trace("thumbnail error="+e);
}
Step 11: Tweening the Images
I'm using Tweener for this tutorial, but there are various tweening engines. If you're not familiar with it
Tweener (caurina.transitions.Tweener) is a Class used to create tweens and other transitions via ActionScript code for projects built on the Flash platform... In layman's terms, Tweener helps you move things around on the screen using only code, instead of the timeline.So first go download it from the google code repository (be sure to get the as3 version) unzip it and put the caurina folder into the same directory as your .fla file, then import the code into your project by placing this in the first line of code:
import caurina.transitions.*;
Then let's have the images fade in once they're loaded, instead of just popping like they are. First we'll set the initial alpha value of the scroller item to 0, so it can fade in properly. Then jump back to our complete event listener handler function and add the Tweener code.
thisOne.alpha = 0;
...
function completeHandler(e:Event):void {
//trace("thumbnail complete "+e.target.loader.parent.parent.title);
Tweener.addTween(e.target.loader.parent.parent, { alpha:1, time: .5 } );
}
Step 12: Resizing the Images
Now, since I resized all my images and created thumbnails beforehand, this step is not as important, but is still great to include so that in case the dimensions change or I add another image later, the scroller will be able to handle it and resize it. The resize function will scale an image to fit within an area as I have explained before on my blog. Basically though, in the completeHandler we'll resize the image to fit an area, even if the image is correctly sized, the resizeMe function will still center the image for us.
function completeHandler(e:Event):void {
//trace("thumbnail complete "+e.target.loader.parent.parent.name);
//size image into scroller
resizeMe(e.target.loader.parent, 140, 105, true, true, false);
Tweener.addTween(e.target.loader.parent.parent, { alpha:1, time: .5 } );
}
//The resizing function
// parameters
// required: mc = the movieClip to resize
// required: maxW = either the size of the box to resize to, or just the maximum desired width
// optional: maxH = if desired resize area is not a square, the maximum desired height. default is to match to maxW (so if you want to resize to 200x200, just send 200 once, or resizeMe(image, 200);)
// optional: constrainProportions = boolean to determine if you want to constrain proportions or skew image. default true.
// optional: centerHor = centers the displayObject in the maxW area. default true.
// optional: centerVert = centers the displayObject in the maxH area. default true.
function resizeMe(mc:DisplayObject, maxW:Number, maxH:Number=0, constrainProportions:Boolean=true, centerHor:Boolean=true, centerVert:Boolean=true):void{
maxH = maxH == 0 ? maxW : maxH;
mc.width = maxW;
mc.height = maxH;
if (constrainProportions) {
mc.scaleX < mc.scaleY ? mc.scaleY = mc.scaleX : mc.scaleX = mc.scaleY;
}
if (centerHor) {
mc.x = (maxW - mc.width) / 2;
}
if (centerVert){
mc.y = (maxH - mc.height) / 2;
}
}
Step 13: Adding Movement
We'll start by just adding the basics. We want users to be able to intuitively control this scroller, so we'll use their mouse position as direction only initially. If the mouse is positioned in the left half of the stage we want to move the scroller to the right, so the user moves in the direction they want the scroller to reveal itself. I prefer this to the direct relationship of moving the scroller the same way the mouse goes, but that's a discussion for another day.
To implement this is pretty simple, we just add an event listener at the end of the buildScroller function, for every time we enter the frame to position the scroller. Then in the handler function we'll create an if/else statement and set the new speed variable depending on mouse position. If the mouse x position is on the left half of the stage, we want the scroller to move one way and if it is on the right half of the stage we want it to move the other direction. Then we apply this speed to the x position of the scroller container. This is now starting to look like something useful because it is now interactive and responsive!
Essentially all we are doing is moving the scroller container clip to one direction of the other, but we're doing it every frame, so it animates the position over time. Remember this scroller clip contains all the items we created, so it's like putting things into a box and moving the box. The items don't have to be moved, just the container! We move it either 5 pixels to the left or 5 pixels to the left every single frame (this 5 could be anything, I just chose 5 'cause it looked right). This leaves a lot to be desired, but it's the first step. We have our scroller moving and have given the end user the power to control this movement!
Notice that we're using the stage properties to find the stage size even though we set the stage size earlier and know exactly what size it is. This is because it will help the code to be self containing and portable. If we were to use the actual values we would have to remember to update them if we ever changed the scroller size or wanted to use this in a different application. We'll create more variables soon that will allow us to fully customize the scroll interaction.
scroller.addEventListener(Event.ENTER_FRAME, moveScrollerThumbs);
trace("termination of build scroller");
...
var speed:Number;
function moveScrollerThumbs(e:Event):void {
if (mouseX < stage.stageWidth/2) { //left half of stage
speed = 5;
}
else { //right half of stage
speed = -5;
}
scroller.x += speed;
}
Step 14: Better Movement - Mouse Limits
You can see above that we still have a way to go with our movement controls of the scroller. There are boundaries to add to the mouse positions that cause the scrolling (we'd like it to only scroll when you are on the stage and over the scroller), limits we'd like to apply to the scroller (so that it doesn't scroll off the page) and we want to make the scroller speed dynamic.
Let's start first by adding a statement to check if we are over the scroller. To do this we'll check the mouseY position, the scroller.y and scroller.height properties. Let's also add a bit more to the mouseX statements, let's ensure that it's a bit more precise and only include the x values from 0 to the center of the stage (stage.stageWidth/2) and from the center to the edge.
function moveScrollerThumbs(e:Event):void {
if ( mouseY > scroller.y && mouseY < scroller.y + scroller.height) { //vertically over scroller
if (mouseX < stage.stageWidth/2 && mouseX > 0) { //left half of stage explicitly
speed = 5;
}
else if (mouseX > stage.stageWidth/2 && mouseX < stage.stageWidth) { //right half of stage explicitly
speed = -5;
}
scroller.x += speed;
}
}
Step 15: Better Movement - Scroller Limits
Now we'll want to apply boundaries to the scroller itself so that it doesn't scroll off the page and leave us with a blank stage and a user who doesn't know or remember which way the images went.
We need to watch the scroller x position and if it goes too far either way reset it to our limit. Remember that the scroller x position is found at its left edge, so when it is at a 0 x position it is flush with the left edge of the stage. So first off if the x position of the scroller is greater than 0 after the speed is calculated and applied, we want to set it back to our limit, 0. This will keep the scroller from moving too far into the stage. We can even add some padding to the stage and since we have the padding between each scroller item of 20, let's make it consistent. Rather than using 0 here, let's make it the same 20 value.
We want the scroller to stop moving left once it's last item is fully on stage. This left limit can be found by finding the width of the scroller and the stage width. If the scroller is moved so far to the left that it's x position is less than it's own width (negative) but factor back in the stage width (plus stageWidth) we need to hold it right there. To add the padding on this end we subtract 20.
function moveScrollerThumbs(e:Event):void {
if ( mouseY > scroller.y && mouseY < scroller.y + scroller.height) {//vertically over scroller
if (mouseX < stage.stageWidth/2 && mouseX > 0) {//left of stage explicitly
speed = 5;
}
else if (mouseX > stage.stageWidth/2 && mouseX < stage.stageWidth) {//right of stage explicitly
speed = -5;
}
scroller.x += speed;
//scroller limits
if (scroller.x < -scroller.width + stage.stageWidth - 20) { //if scroller too far left
scroller.x = -scroller.width + stage.stageWidth - 20;
}
else if (scroller.x > 20) { //if scroller to far right
scroller.x = 20;
}
}
}
Step 16: Dynamic Movement
To make the movement more fluid and dynamic we only need to update two lines of code. You guessed it, the two lines that set the speed need to be adjusted to determine the speed by the distance mouseX is from the center of the stage rather than a flat value of 5. We find the distance the mouse is horizontally from the center axis of the stage (stage.stageWidth/2), and we want the scroller to move to the right if we're on the left side and vice versa. In both instances we find the negative difference between the two. Then we should scale it down by dividing it by, let's try 8, because if we don't, the scroller is way too fast (go ahead, try it).
function moveScrollerThumbs(e:Event):void {
if ( mouseY > scroller.y && mouseY < scroller.y + scroller.height) {//vertically over scroller
if (mouseX < stage.stageWidth/2 && mouseX > 0) {//left of stage explicitly
speed = -(mouseX - stage.stageWidth/2) / 8;
}
else if (mouseX > stage.stageWidth/2 && mouseX < stage.stageWidth) {//right of stage explicitly
speed = -(mouseX - stage.stageWidth/2) / 8;
}
scroller.x += speed;
//scroller limits
if (scroller.x < -scroller.width + stage.stageWidth - 20) { //if scrolled too far left
scroller.x = -scroller.width + stage.stageWidth - 20;
}
else if (scroller.x > 20) { //if scrolled to far right
scroller.x = 20;
}
}
}
Step 17: User Friendly Movement
Now test this and think about your end user. You'll be looking at this image scroller and maybe wanting to look a bit closer or focus in on an image that catches your eye. You want this scroller to stop moving, (if I move to the center of the stage it still drifts to one side of the other) and I focus more on fighting the scroller than I do on the image I want to look at.
This is an example of a non-transparent user control. We want the user to be able to intuitively use this scroller to the point that they don't even think about it. If the user has to think about it, or even worse, fight your app to get it to do something, you lose and they will look elsewhere for their content. So let's avoid the struggle and give the scroller a dead area at the center of the stage. Then they can rest at ease by moving to the center of the scroller to inspect something or stop the motion sickness. It's a really easy thing to do and has a big payoff, making the scroller easier to use. Rather than using the center of the stage for all our calculations we'll give it some padding.
function moveScrollerThumbs(e:Event):void {
if ( mouseY > scroller.y && mouseY < scroller.y + scroller.height) {//vertically over scroller
if (mouseX < stage.stageWidth/2 - 40 && mouseX > 0) {//left of stage explicitly
speed = -(mouseX - (stage.stageWidth/2 - 40)) / 8;
}
else if (mouseX > stage.stageWidth/2 + 40 && mouseX < stage.stageWidth) {//right of stage explicitly
speed = -(mouseX - (stage.stageWidth/2 + 40)) / 8;
}
else {
speed = 0; //if in the center area, clear the speed to 0 so we don't have any roll over effect from the last frame.
}
scroller.x += speed;
//scroller limits
if (scroller.x < -scroller.width + stage.stageWidth - 20) { //if scrolled too far left
scroller.x = -scroller.width + stage.stageWidth - 20;
}
else if (scroller.x > 20) { //if scrolled to far right
scroller.x = 20;
}
}
}
Step 18: Abstracting Movement More
Let's abstract this out a bit. We have multiple places where we are adding padding to values or measurements. It'd be best practice to create a variable to hold our padding value and apply the same value easily by applying the variable every time we want padding. This would help us update if a client later says "I like it, but can we spread it out some?" or the dreaded... "Something's off about this...", then we'd just update the variable and it would be ready to go. I always try to create controls for myself, the programmer, to make it easy to customize a project.
I've created a variable named "padding" here and am applying it everywhere that makes sense. Notice that in a couple places I multiply it by 2, use your own judgment here - if you want more padding give it more, of course we could take it further and have a "padding_small" variable and another "padding_large" variable, but one will do justice for the purpose of this tutorial. You get the idea. This doesn't really change the end file, but it can help to make your life much easier and your code simpler, more understandable or readable.
import caurina.transitions.*;
//load xml
var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
xmlLoader.addEventListener(Event.COMPLETE, LoadXML);
var xmlPath:String = "image-scroller.xml";
xmlLoader.load(new URLRequest(xmlPath));
trace("loading xml from: " + xmlPath);
function LoadXML(e:Event):void {
trace("xml loading complete");
xmlData = new XML(e.target.data);
//trace(xmlData.image);
buildScroller(xmlData.image);
}
var scroller:MovieClip = new MovieClip();
var speed:Number;
var padding:Number = 20;
this.addChild(scroller);
scroller.y = scroller.x = padding;
//build scroller from xml
function buildScroller(imageList:XMLList):void{
trace("build Scroller");
for (var item:uint = 0; item < imageList.length(); item++ ) {
var thisOne:MovieClip = new MovieClip();
//outline
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
thisOne.addChild(blackBox);
thisOne.x = (140 + padding) * item;
thisOne.itemNum = item;
thisOne.title = imageList[item].attribute("title");
thisOne.link = imageList[item].attribute("url");
thisOne.src = imageList[item].attribute("src");
//image container
var thisThumb:Sprite = new Sprite();
//add image
var ldr:Loader = new Loader();
var urlReq:URLRequest = new URLRequest(thisOne.src);
trace("loading thumbnail "+item+" into Scroller: " + thisOne.src);
ldr.load(urlReq);
//assign event listeners for Loader
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
ldr.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
thisThumb.addChild(ldr);
thisOne.addChild(thisThumb);
//create listeners for this thumb
thisOne.buttonMode = true;
thisOne.addEventListener(MouseEvent.MOUSE_OVER, overScrollerItem);
thisOne.addEventListener(MouseEvent.MOUSE_OUT, outScrollerItem);
thisOne.addEventListener(MouseEvent.CLICK, clickScrollerItem);
//add item
scroller.addChild(thisOne);
}
scroller.addEventListener(Event.ENTER_FRAME, moveScrollerThumbs);
trace("termination of build scroller");
}
function overScrollerItem(e:MouseEvent):void {
trace("over" + e.currentTarget.name);
}
function outScrollerItem(e:MouseEvent):void {
trace("out" + e.currentTarget.name);
}
function clickScrollerItem(e:MouseEvent):void {
trace("clicked item " + e.currentTarget.itemNum + " - visit url: " + e.currentTarget.link);
}
function completeHandler(e:Event):void {
//trace("thumbnail complete "+e.target.loader.parent.parent.name);
//size image into scroller
resizeMe(e.target.loader.parent, 140, 105, true, true, false);
Tweener.addTween(e.target.loader.parent.parent, { alpha:1, time: .5 } );
}
function errorHandler(e:IOErrorEvent):void {
trace("thumbnail error="+e);
}
//The resizing function
// parameters
// required: mc = the movieClip to resize
// required: maxW = either the size of the box to resize to, or just the maximum desired width
// optional: maxH = if desired resize area is not a square, the maximum desired height. default is to match to maxW (so if you want to resize to 200x200, just send 200 once)
// optional: constrainProportions = boolean to determine if you want to constrain proportions or skew image. default true.
function resizeMe(mc:DisplayObject, maxW:Number, maxH:Number=0, constrainProportions:Boolean=true, centerHor:Boolean=true, centerVert:Boolean=true):void{
maxH = maxH == 0 ? maxW : maxH;
mc.width = maxW;
mc.height = maxH;
if (constrainProportions) {
mc.scaleX < mc.scaleY ? mc.scaleY = mc.scaleX : mc.scaleX = mc.scaleY;
}
if (centerHor) {
mc.x = (maxW - mc.width) / 2;
}
if (centerVert){
mc.y = (maxH - mc.height) / 2;
}
}
function moveScrollerThumbs(e:Event):void {
if ( mouseY > scroller.y && mouseY < scroller.y + scroller.height) {//vertically over scroller
if (mouseX < stage.stageWidth/2 - padding*2 && mouseX > 0) {//left of stage explicitly
speed = -(mouseX - (stage.stageWidth/2 - padding*2)) / 8;
}
else if (mouseX > stage.stageWidth/2 + padding*2 && mouseX < stage.stageWidth) {//right of stage explicitly
speed = -(mouseX - (stage.stageWidth/2 + padding*2)) / 8;
}
else {
speed = 0;
}
scroller.x += speed;
//scroller limits
if (scroller.x < -scroller.width + stage.stageWidth - padding) { //if scrolled too far left
scroller.x = -scroller.width + stage.stageWidth - padding;
}
else if (scroller.x > padding) { //if scrolled to far right
scroller.x = padding;
}
}
}
Step 19: Adding Scale Tweens
With this idea of using variables to customize our file, let's add some bling with the Tweener library we already added. It's great to call out the item in the scroller the user rolls over, so let's have it scale a bit larger to help it stand out. This is where the beauty of Tweening comes in. Just one line of code, some variables and we can tell it to tween the scaleX and scaleY of the scroll item. If we're scaling the item we'll want to also translate its coordinates a bit so the item is still centered, this looks like tricky math, but it's actually pretty simple.
First, we add a variable to each object to store it's starting x position, we can just call it myx and put it on the same line where we calculate the x (we do this so we can reliably move the item back to it's rightful place when we need to). We need to move it so the item is still centered, so we find its current width and factor it by half the scale we are applying. We do this for x and y. Then we need to tween it back to its starting size and coordinates on the mouse out event handler.
thisOne.x = thisOne.myx = (140 + padding) * item;
...
var thumbSmall:Number = 1;
var thumbLarge:Number = 1.1;
function overScrollerItem(e:MouseEvent):void {
//trace("over" + e.currentTarget.name);
Tweener.addTween(e.currentTarget, { scaleX:thumbLarge, scaleY:thumbLarge, x:e.currentTarget.myx - e.currentTarget.width * Math.abs(thumbSmall - thumbLarge)/2, y: -e.currentTarget.width * Math.abs(thumbSmall - thumbLarge)/2, time:1 } );
}
function outScrollerItem(e:MouseEvent):void {
//trace("out" + e.currentTarget.name);
Tweener.addTween(e.currentTarget, { scaleX:thumbSmall, scaleY:thumbSmall, x:e.currentTarget.myx, y:0, time:1 } );
}
Step 20: Adding Border Tweens
Let's make them stand out even more by fading the white 'blackBox' around the thumbnails (just realizing that I named the white box black =) ). This one is easier than the previous step. Just add a couple of variables for the fade in and out values (alpha) and add the Tweens to both the over and out event listener handler function. We'll also need to ensure that we can address the blackBox sprite so when we initialize it let's explicitly name it. While we're at it we can set the initial alpha of the box to equal the thumbFadeOut variable.
//outline
var thumbFadeOut:Number = .2;
var thumbFadeIn:Number = 1;
var thumbSmall:Number = 1;
var thumbLarge:Number = 1.1;
...
var blackBox:Sprite = new Sprite();
blackBox.graphics.beginFill(0xFFFFFF);
blackBox.graphics.drawRect( -1, -1, 142, 107);
blackBox.alpha = thumbFadeOut;
thisOne.addChild(blackBox);
thisOne.blackBox = blackBox;
...
function overScrollerItem(e:MouseEvent):void {
//trace("over" + e.currentTarget.name);
Tweener.addTween(e.currentTarget, { scaleX:thumbLarge, scaleY:thumbLarge, x:e.currentTarget.myx - e.currentTarget.width * Math.abs(thumbSmall - thumbLarge)/2, y: -e.currentTarget.width * Math.abs(thumbSmall - thumbLarge)/2, time:1 } );
Tweener.addTween(e.currentTarget.blackBox, { alpha:thumbFadeIn, time: 1 } );
}
function outScrollerItem(e:MouseEvent):void {
//trace("out" + e.currentTarget.name);
Tweener.addTween(e.currentTarget, { scaleX:thumbSmall, scaleY:thumbSmall, x:e.currentTarget.myx, y:0, time:1 } );
Tweener.addTween(e.currentTarget.blackBox, { alpha:thumbFadeOut, time: 1 } );
}
Step 21: Adding Link on Click
Speaking of all the event listeners, we still haven't finished coding the click event. We want to have the click basically be a link click and link the user to the url specified in the xml. For this, we just pass the link value from the event's target that we are currently tracing, to a URLRequest object and try to navigate to it with our friend navigateToURL. There we have it.
function clickScrollerItem(e:MouseEvent):void {
//trace("clicked item " + e.currentTarget.itemNum + " - visit url: " + e.currentTarget.link);
var urlRequest:URLRequest = new URLRequest(e.currentTarget.link);
try {
navigateToURL(urlRequest);
}
catch (e:Error) {
// handle error here
trace(e);
}
}
Conclusion
I hope you enjoyed this tutorial and that you learned something. You should now have a pretty good grasp on AS3 and xml, loading and resizing images, easy animating with tweening, mouse event listeners, scrolling and user friendly interactive design!
Was it easy to follow? I usually do tutorials in open source examples so I don't know if I droned on too much here.. Feel free to let me know in the comments. This tutorial is meant to be a spring board for ideas; there are tons of other things we could to with this file and get many other effects going, but this should get you started!
















User Comments
( ADD YOURS )Fred August 26th
Very nice tutorial.
( )My only criticism would be that it would have been nicer if it were a class.
MSFX August 26th
some smoothing on the images would make the scaling look SO much nicer..
( )André August 26th
Wow
nice but too many codes!!
Thanks anyway!!
( )Chris August 26th
looks good, will try it later
( )Dario Gutierrez August 26th
Nice, maybe a little fast but excellent.
( )Franky August 26th
Great into to XML parsing
( )jamie August 26th
Very nice, I have tried this with only 3 images however and the thumbnails jump around all over the place. Maybe I should try to change the dimensions of the box?
( )jamie August 26th
also the scroller gets added to the stage with this.addChild(scroller); right?
( )I have a movie clip I’v added to the stage balled box_mc. How would I add the scroller so that is goes inside that movie clip and not the stage?
Evan Mullins August 27th
@Jamie –
Yea, the dimensions of your box/stage should be larger than the scroller, so updating the dimensions will help with only having 3 images. If the scroller.width is less than the stage width, I can see that freaking out the interactive code…
To add the scroller to box_mc rather than the stage you’d code: box_mc.addChild(scroller);
teo September 6th
if you don’t want to change the dimensio of yor box/stage and have only 3 images you could add:
if (scroller.width < stage.stageWidth) {
scroller.x = padding;
}
inside the function moveScrollerThumbs
this'd prevent the code to freak out …
Alex August 26th
Good tutorial, very thorough and teaches a lot of different basic skills for working in flash. I actually had just learned XML parsing and adding images the other night so this was just in time. For anyone looking to add smoothing to the images just add these two lines in the ‘completeHandler’ function:
var image:Bitmap = Bitmap(e.target.content);
image.smoothing = true;
That should smooth out the images when they resize.
( )taha August 28th
Thanks for the tip, works great and loooks lovely !!
( )alexandru asmarandei August 27th
hello,
( )very nice tut, very well explained. i do have one question. let’s say i already have created a ’scroller’ which has a nice look, and i’ve already placed some containers for thumbnails in the scroller. what would be the simpliest code to load pictures from xml into those specific containers? (everything else, including movement is done separately)
i will start with this:
var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
var xmlPath:String = “pictures_xml.xml”;
xmlLoader.load(new URLRequest(xmlPath));
this part is enough for loading the xml, correct? (let’s say that the pictures are small enough so i will not need the complete event listener.)
what i need now is to put the infos from xml into my scroller and make so that when i press one image i got the big one loaded into a different container. and here i got stucked.
can you, please, help me? you can name however you want the containers and the scroller.
thanks you very much,
alex
Evan Mullins August 27th
@alexandru –
Your wanting to load the larger image into a container in flash rather than opening up the link in the browser? You shouldn’t have to do much at all with the xml parsing, just update the click event listener (found in step 21) to load the image internally rather than navigateToURL. Good luck!
( )Vincent D'Amico August 27th
Some of the tutorials have good functionality lesson’s. Even though the functionality is there. Standard designed features should be shown. Not left in the air.
There are basic thing’s a designer for instance would like, I wish you extended this lesson to include the click event listener to load internally, thanks.
You did a great job.
Vincent D'Amico August 27th
I love the Milestone Execution of this tutorial. Great Work!
( )taha August 28th
wow , amazing tutorial !!
thanks, it was very informative and useful for me !
ur blog, goes to my bookmarks, i will just start exploring it !
( )Clay Jackson August 29th
Thanks! Will use this.
( )Prabhu August 31st
Great nice work!!! it is amazing work you have done. I have a small query on this; is that we can have an auto Scroll on this?
( )Jerome Frederick September 1st
Awesome tutorial! Say I wanted to make the Url open up on the current page instead of a new one. What would i have to edit. Thanks in advance
( )Jerome Frederick September 1st
I figured it out, thanks anyway!
( )ashishnamdeo September 2nd
good tutorial
( )cain September 2nd
yes it’s good thanks for the tutorial, If I may have a request it would be more sensible if the previe of the big picture is in the same page like in the buttom or at the middle or so
( )Fabian September 4th
Great Tuturial!!!
But i got A problem: if i change: var thumbLarge:Number = 1.1; to a valua a lot bigger (for example 1.6) the scale will work, but i cant see the full image because the ones which have been loaded later will hide it.
i know i have to add the current image again so that it will be on top of the display list, but i don´t know how to manage this
maybe you can help me with this problem
thanks a lot
( )Michael Tully September 7th
Fantastic TUT mate,
Would be better a cleaner put in to a class, one question i have would be how to get this to take the full width of any screen and still scroll correctly.
Could anyone shed abit of light on to this
( )Hussein September 8th
brilliant mate – learned a lot from this, everything is well explained thanks a lot for sharing
( )Ajit September 17th
That seems to be a nice tutorials and i also want to do some stuff like this i m new to flash action script so plz suggest me some better way of getting started with flash action script i am waiting for your reply, you can mail me at ajitajoit@gmail.com.
( )Jared September 18th
Thanks! Awesome tutorial.
It really helped with a lot of technical things I was struggling with. (especially since it turns out Im working on a project that requires a thumbnail scroller, hence landing on this page, obviously!
) One question though… the thumbnails dont show any loading progress. What would you recommend as the simplest way to add a load-progress feature to each of the thumbnails?
A little progress loader per thumbnail would really finish off this tutorial perfectly for me!
If you have time, I would love a little advice and help with that! Much appreciated. You can send it to my email. Thanks
( )Abhilash Oleti September 29th
very nice tutorial, but along with this i want image title also has to appear below the image, can u help me out….
( )jdinh October 6th
Great tutorial!
I was wondering if you can tell me how to display the “title” under each thumbnail. It’ll be great if you can help me out THANKS!
Your Awsome!
( )Pablo Brandon October 13th
Hi thaks for the great tutorial! It reaaly helped me out!
The scroller works great by itsef.
But I’m having problems when import the scoller as an external SWF to a main site. When i test the site, the scroller dosen’t scroll its whole extension to show all the thumbs in line. Is the scroller mixing it’s stage (mouse control) setting with the main site’s stage size (1600X1200)?
I dont know very much about coding, how coud is fix this?
Your Awsome!
( )Justinho October 15th
Really great tutorial Evan!! I found it really useful!
I haven’t followed it word for word as its not quite right for an image library I’m trying (understandably).
I’ve done the loading slightly differently, I split the code into to a thumbnail and a document class, I’ve also used a different tween engines plus some other bits.
I’ll look out for any other tutorials u have!
( )Sjef October 15th
Thanks for this great tutorial! Only there is one thing I don’t understand(maybe it’s because I’m new and not (yet) familiar with AS3 and Flash).
I want to place this scroller in a Movieclip with specified boundaries. I already read the option a few comments earlier to add the scroller to box_mc.addChild(scroller) instead of this.addChild(scroller). If I do so then the scroller doesn’t scroll and the right side doesn’t stop showing at the end of the Movieclip boundary, but at the end of the stage. So I was wundering if there is a way to put this scroller in a box and doesn’t scroll page width but box width?
Thanks for your help!
( )Sjef October 16th
Well, i’m a bit further. I discovered the place in the code to specify the area where the mouse have effect. I also discovered the place where I can specify the limits of the scrollbar (max left, max right). They are all set in the right position.
( )Now, I only need to let the scroller show in the box_mc and not on the whole stage width. Anyone who can help me? Thanks!
Anas manaa October 21st
Wonderfull tutorial
thank you very much
( )bruno October 23rd
Excellent !
( )But to fast for me in the resizeMe function (conditions details (the first one)) I would appriciate more details.
Result looks better with a LineStyle property applied at the “BlackBox”.
Anyway Thanks ……..
suzi October 26th
Is there a way to load this into another fla file? I followed this tutorial and created the scroller, but now I want to add it to another flash project I have. I tried importing the f4v but it just shows the static background. I’m not even sure if that’s the right way to go about it but it’s the only thing I could think of. If anybody could let me know about this it would be a big help, thanks.
( )Sreelash November 9th
THank you very. Understanding tutorial. Actually i was confused with using multiple movieclips using actionscript3. I understood.Thanks a lot.
( )