How to Position Dynamically Loaded Content Based on Browser Resizing

ActionScript 2.0

In one of the recent comments to “How to Position Movie Clips Based on Browser Resizing,” a look at the ActionScript 2.0 Stage.onResize event, reader Eddy asked about adjusting an image loaded at runtime; particularly, about fading in an image set to scale and reposition itself based on the size of the browser.  I was originally going to reply to his comment directly, but this seems to me like something that would make a decent blog entry of its own, so here’s one particular stab at it. 

Using a two container approach

The trick to fading in one item over another is to change the alpha transparency of the upper item.  If you’re fading in (as opposed to fading out), that means changing the alpha from zero to 100 (we’re talking AS2, by the way; in AS3, that would be zero to one).  If you’re fading in something that’s loaded from the outside, you have to wait until the external item — here, it’ll be a JPG — has fully loaded.  If you’re publishing to Flash Player 7 or higher, one of your options is the MovieClipLoader class, so we’ll use that for this walkthrough.  If you’ve read “How to Position Movie Clips Based on Browser Resizing,” then some of this will look familiar.

Here’s the first chunk of ActionScript 2.0:

import mx.transitions.Tween;
import mx.transitions.easing.*;

Stage.scaleMode = "noScale";
Stage.align = "TL";

var container:MovieClip = this.createEmptyMovieClip("mcContainer", 0);
var pictA:MovieClip = container.createEmptyMovieClip("mcPictA", 0);
var pictB:MovieClip = container.createEmptyMovieClip("mcPictB", 1);

The first two lines import the Tween class and all the tween-related easing classes in the transitions.easing package.  After that, the Stage class’s scaleMode and align properties are specifically set to values that allow for programmatic response to changes in the SWF’s dimensions — such as when the user resizes the browser — when the SWF is embedded at 100% width and height in the HTML.

Finally an arbitrarily named variable, container, is declared and set to an instance of the MovieClip class.  In AS2, this happens by way of the MovieClip.createEmptyMovieClip() method, as invoked on the global this property.  In this context, the term this refers to the main timeline, because that’s where this code is located.  The second parameter, 0, refers to the depth of this movie clip, which is the lowest possible depth a dynamically created movie clip can hold.

The new movie clip is given the instance name mcContainer, but the variable container makes an equally valid reference.  Inside this container clip, two additional containers are created in the same way.  Their references are pictA and pictB, respectively, and they’re set to depths 0 and 1 inside container.

We need a bit more prep work before we can dive into the fun stuff, so add the following new ActionScript:

var root:MovieClip = this;
var firstRun:Boolean = true;
var currentPict:String = "A";
var tw:Tween;

var listener:Object = new Object();
listener.onResize = positionContent;
listener.onLoadInit = loadHandler;

In order to make the main timeline easier to reference in later code, a variable named root is declared and set to the term this (again, a reference to the main timeline).  A firstRun variable holds the Boolean value true and will act as a signpost to determine whether or not code is being executed on the user’s first arrival.  The currentPict variable currently refers to the string “A” (yup, just the letter A) and will be used to toggle back and forth between the inner containers pictA and pictB.  A Tween instance (tw) is declared, but not yet set to anything; and finally, a listener variable is declared and set to an instance of the Object class.  Every one of these variables is arbitrarily named.

The listener variable will act on behalf of the Stage class and the MovieClipLoader instance (whose creation is your next step).  Two properties are assigned to listener:  onResize (that is, the Stage.onResize event) and onLoadInit (that is, MovieClipLoader.onLoadInit).  Each of these properties stores a reference to a custom function — positionContent() and loadhandler() — that will be executed as the events occur.  You’ll see in a moment how the MovieClipLoader instance and the Stage class will add the listener variable to their VIP lists.

When the user resizes the browser, which in turn resizes the SWF, a Stage.onResize event will be dispatched.  The listener variable’s onResize property will trigger the positionContent() function, which will move around the desired content.  Same idea for the MovieClipLoader instance, which keeps track of loaded movie clips and image files.

Let let’s get to it.  Here’s some more code:

var mcl:MovieClipLoader = new MovieClipLoader();
mcl.loadClip("firstImage.jpg", pictA);
mcl.addListener(listener);

Here, an arbitrarily named variable, mcl, is declared and set to an instance of the MovieClipLoader class.  The MovieClipLoader.loadClip() method is invoked on mcl, which is instructed to load a file named firstImage.jpg into the pictA inner container clip.  In the very next line, the MovieClipLoader.addListener() method is invoked on mcl, which causes mcl to keep tabs on the listener object.  That means we need to write a loadHandler() function, so that Flash knows what to do when firstImage.jpg arrives.  Here’s that function:

function loadHandler():Void {
  if (firstRun) {
    Stage.addListener(listener);
    positionContent();
    firstRun = false;
  }
  pictA.swapDepths(pictB);
  tw = new Tween(root["pict" + currentPict], "_alpha", Strong.easeOut, 0, 100, 2, true);
  togglePicts();
}

This gets a tad knotty, so let’s step through it carefully.  Remember that firstRun variable?  Here’s where it comes into play.  If this is the first time the loadHandler() function is being executed — that is, firstRun is true — then the Stage.addListener() method tells the Stage class to keep track of listener, just like mcl is doing.  In effect, this means that the positionContent() function will now be executed whenever the SWF resizes.  (Don’t worry, positionContent() hasn’t been shown yet, but it’s coming in a few paragraphs.)  For good measure, the positionContent() function is called at this point, too, just to position the desired movie clip(s) even if the browser hasn’t been resized.  Then firstRun is set to false, because the previous two lines only need to be run during the visitor’s first encounter with the SWF.

After that if statement, the MovieClip.swapDepths() method is invoked on pictA, swapping that inner container with its sibling, pictB.  If you scroll up a bit, you’ll see that pictA was created first, at a depth of 0 inside the container clip (that is, below pictB).  This swapDepths() method now puts pictA above its sibling at depth 1, and consequently moves pictB to depth 0.

The tw variable is now set to a new Tween instance that changes the MovieClip._alpha property of pictA from 0 to 100 over two seconds.  The first parameter of this Tween constructor function is probably the most interesting.  The expression is root["pict" + currentPict] … so what does that mean?  Well, remember, root simply refers to the main timeline, which contains a handful of variables, including the variables pictA and pictB.  So the main timeline is our reference point.  From the main timeline’s point of view, there are two variables that start with the string “pict” (which is the next part of the expression), and finally, the value of currentPict happens to be “A”, so the full expression effectively reads root.pictA.  From the main timeline’s point of view, that simply means pictA, which refers to the inner container clip that has just been instructed to fade itself in from zero to 100.

Why use a made-up variable, here, instead of _root?  For discussion on that question, see “Is _root Evil?”  For more information on this brackets ([]) approach to targeting variables, see “How to Reference Objects Dynamically.”  The reason a dynamic reference is useful here is because we can simply change the value of currentPict from “A” to “B”, and back again, to keep flip-flopping which container is the currently operative movie clip.  Finally, a custom togglePicts() function is called, which does just that (changes “A” to “B”, or vice versa).

Here’s the positionContent() function:

function positionContent():Void {
  container._width = Stage.width;
  container._yscale = container._xscale;
}

This can get as crazy or simple as you like.  In this case, the width of container clip is set to the width of the Stage, and its _yscale is set to the same value as its _xscale, which simply ensures that container maintains its current aspect ratio when scaled.  The fading and depth swapping that occurs with pictA and pictB isn’t affected by the positioning of container.

Here’s the togglePicts() function:

function togglePicts():Void {
  if (currentPict == "A") {
    currentPict = "B";
  } else {
    currentPict = "A";
  }
}

Pretty straightforward.  Now, in Eddy’s question, the website provided a way to change the background image.  Let’s step through how that might happen.  Assuming a button symbol with the instance name myButton, take a look at the following code:

myButton.onRelease = buttonHandler;
function buttonHandler():Void {
  mcl.loadClip("theSecondImage.jpg", root["pict" + currentPict]);
}

The Button.onRelease event is assigned to a custom function named buttonHandler().  The buttonHandler() function invokes MovieClipLoader.loadClip() on the mcl instance, passing it a new JPG to load, and a new container in which to load it.  Remember, at this point, the custom loadHandler() function has already been called (it was triggered when theFirstImage.jpg loaded).  The last thing that function does is call togglePicts(), which changes the value of currentPict from “A” to “B”.  At this point, then, the value of root["pict" + currentPict] actually points to pictB.  The mcl instance is already listening to listener, so when theSecondImage.jpg loads, loadHandler() will be called again.  Here’s that code again, just for reference:

function loadHandler():Void {
  if (firstRun) {
    Stage.addListener(listener);
    positionContent();
    firstRun = false;
  }
  pictA.swapDepths(pictB);
  tw = new Tween(root["pict" + currentPict], "_alpha", Strong.easeOut, 0, 100, 2, true);
  togglePicts();
}

The code inside the if statement won’t run this time, because firstRun is false.  The swapDepths() line will swap pictA and pictB, putting pictB back on top.  The tw variable will be set to a new instance of the Tween class.  Even though the code doesn’t change, the value of root["pict" + currentPict] has changed, which means it’s now pictB that will be tweened from an alpha of zero to 100, thus fading in pictB.  After that, togglePicts() changes currentPict from “B” back to “A” and the cycle can easily repeat again, for as many images as you want to load.

Eddy could, for example, offer a second button with this code:

myButton2.onRelease = buttonHandler2;
function buttonHandler2():Void {
  mcl.loadClip("theThirdImage.jpg", root["pict" + currentPict]);
}

Just another Button.onRelease event handler.  Here, a third image is loaded, and the container it’s loaded into is — as it happens — pictA (simply because the value of currentPict is “A” again).  The inner two containers, pictA and pictB, keep swapping depths as needed, and the value of currentPict always ensure that the upper container is the one that fades in.

Here’s the full code discussed, all on one grand swoop.

import mx.transitions.Tween;
import mx.transitions.easing.*;

Stage.scaleMode = "noScale";
Stage.align = "TL";

var container:MovieClip = this.createEmptyMovieClip("mcContainer", 0);
var pictA:MovieClip = container.createEmptyMovieClip("mcPictA", 0);
var pictB:MovieClip = container.createEmptyMovieClip("mcPictB", 1);

var root:MovieClip = this;
var firstRun:Boolean = true;
var currentPict:String = "A";
var tw:Tween;

var listener:Object = new Object();
listener.onResize = positionContent;
listener.onLoadInit = loadHandler;

var mcl:MovieClipLoader = new MovieClipLoader();
mcl.loadClip("a.gif", pictA);
 mcl.addListener(listener);

function loadHandler():Void {
  if (firstRun) {
    Stage.addListener(listener);
    positionContent();
    firstRun = false;
  }
  pictA.swapDepths(pictB);
  tw = new Tween(root["pict" + currentPict], "_alpha", Strong.easeOut, 0, 100, 2, true);
  togglePicts();
}

function positionContent():Void {
  container._width = Stage.width;
  container._yscale = container._xscale;
}

function togglePicts():Void {
  if (currentPict == "A") {
    currentPict = "B";
  } else {
    currentPict = "A";
  }
}

myButton.onRelease = buttonHandler;

function buttonHandler():Void {
  mcl.loadClip("b.gif", root["pict" + currentPict]);
}

Leave a Reply