In Flash, a preloader is a code module that pauses a movie until some required body of data has finished downloading, guaranteeing the proper playback of the movie. For example, a preloader can ensure that animation or sound has sufficiently buffered before playing, or that a series of variables has loaded before being manipulated and displayed. For each of these types of data, a different technique is used to build the corresponding preloader. Over the course of this article we'll look at those techniques, starting with the simplest: a preloader for a single movie. Note that the code examples in this article all use Flash 5 ActionScript syntax.
The preloader examples described in this article are available for download here. Note that the music player example has been extended to provide a draggable playhead (not covered in this article).
A single Flash .swf file is a self-contained unit. Its
contents--graphics, sound, movie clips, buttons, and scripts--are dispersed
across the frames of its main timeline. By monitoring the download progress of
the main timeline's frames, we can prevent a movie from playing before
adequate content is available. When enough frames have loaded, we play the
movie. Follow these steps to create a basic, single-movie preloader:
|
Related Reading
ActionScript: The Definitive Guide
Table of Contents
Index Sample Chapter Author's Article Read Online--Safari Search this book on Safari: |
Create a new movie.
Add 100 frames to the default Layer 1 layer (you can actually
use any number greater than 15, but we'll stick to 100 for this
example).
At frame 15 of the Layer 1 layer, add a blank keyframe
(Insert-->:Blank Keyframe)
Between frames 15 and 100 of the Layer 1 layer, add keyframes
with plenty of content (sound and bitmaps are nice and bulky).
On the main timeline, create a new layer called
scripts.
On the main timeline, create a new layer called
labels.
At frame 4 of the labels layer, add a blank keyframe.
Use Modify-->Frame to label the new keyframe
loading.
At frame 15 of the labels layer, add a blank keyframe.
Label the new keyframe beginMovie.
At frame 5 of the scripts layer, add a blank keyframe, and
attach the following code to it:
// Specify how many frames to load before playing.
var loadAmount = _totalframes;
// If the required number of frames have finished loading...
if (_framesloaded == loadAmount) {
// ...start the movie
gotoAndPlay("beginMovie");
} else {
// ...otherwise, go back and check load progress again.
gotoAndPlay("loading");
}
That's it, your preloader is done. Now you need to make sure it works. To simulate movie download at various modem speeds we use a testing tool called the Bandwidth Profiler, available in Test Movie mode. Here's how to turn the Bandwidth Profiler on:
Keep an eye on the green bar at the top of the Bandwidth Profiler. It shows
the simulated download progress of the movie. Notice that playback pauses at
frame 4 while your movie loads. If the delay is too long at the target speed,
you should consider splitting your movie into pieces. Add multiple
preloaders to your main movie timeline or separate your content into
individual .swf files which you load only as necessary (we'll cover
preloading multiple .swf files later). Note that the Bandwidth
Profiler can only be used to test single-movie preloaders; it doesn't show the
progress of loaded variables or XML, or movies loaded via
loadMovie().
To see several inventive examples of preloaders, visit Colin's recently launched companion site for ActionScript: The Definitive Guide. The book's trailer movie uses eye candy (rotating 3-D orbs) as a preloader, while the Flash site itself transforms loading bars into graphical elements in a navigation menu. (Of course, Flash is required.)
Now that you've seen your preloader in action with the Bandwidth Profiler,
let's consider how it works by examining the code you attached to frame 5.
In its first statement, we create a variable, loadAmount, that
indicates how many frames we're going to preload.
var loadAmount = _totalframes;
In our example, we set loadAmount to _totalframes, a
built-in ActionScript property that stores the number of frames in a movie
clip or main timeline. By using _totalframes, we keep our code
generic--the number of frames we want to preload is the number of frames in the
timeline, as determined by ActionScript.
However, we should remember that Flash streams content (it can play a
movie while it continues to load). It is often wise to preload only part of a
movie, allowing the rest to load while the user is occupied watching the
show. To preload only a portion of a movie, we set loadAmount to
some number less than _totalframes. Use the Bandwidth Profiler to
determine the appropriate portion to preload at your site's target modem
speed.
Having decided how many frames to preload (in our case, all of them), we
next check whether that amount has finished downloading or not. We compare the
built-in property _framesloaded (which tells us how many frames have
loaded) with loadAmount (which indicates how many frames must be
loaded before the movie plays):
if (_framesloaded == loadAmount) {
If the two values are equal, the required frames have loaded, so we can start playing the movie:
gotoAndPlay("beginMovie");
But if the two values are not equal, the required frames have not loaded, so
we send the playhead back to the loading frame (frame 4 in our
example):
gotoAndPlay("loading");
There, the movie resumes play, and re-enters frame 5, where we check how
many frames have loaded again. The playhead, therefore, is stuck in a timeline
loop until the required number of frames have loaded, at which point it safely
proceeds to the beginMovie frame. Simple, but effective.
Some notes about the technique:
Our preloader starts at frame 5 rather than frame 1 because scripts on the first frame of a movie are very occasionally skipped by the Flash player. Leaving some room before our preloader also makes adding new content to the start of the movie easier.
To perform our preload check less frequently we can add new frames between frames 4 and 5. This might be handy if we're playing a preload animation or sound that should terminate only at a specific point.
We don't use the old-school ifFrameLoaded statement in our
preloader. That's been deprecated and is less flexible than our code.
Having just built a preloader that starts at frame 5, you might be wondering what would happen if there were content on frames 1 through 4. If a frame is not fully loaded when Flash attempts to render it, its contents are displayed one layer at a time as they load. Layers load from either the bottom up or the top down, depending on the load order set for the movie under File-->Publish Settings-->Flash-->Load Order.
We can use this to our advantage to build a "pre-preloader". By simply separating, say, a logo, onto individual layers, we can create a basic animation that indicates initial load progress to the user. In Figure 1, the letters in the word "moock" are each placed on their own layer, resulting in the animation depicted in Figure 2.

Figure 1: Separating content onto layers.

Figure 2: The progressive display of content on
layers.
A simple animation based on layer load order tells the user that the movie has started downloading, but it doesn't indicate how long they have left to wait. We'll learn how to do that next.
In our first attempt at a preloader, we successfully delayed the playback of a movie until a specified number of frames had downloaded. We'll now see how to show some signs of life to the user while our movie loads (otherwise the Flash Player may look conspicuously stalled). We'll add the following features to our single-movie preloader (shown in Figure 3):

We've got some Flash production work to do, and then we need to update our preload script. We'll start by creating layers on the main timeline to hold the progress-display content:
On the main timeline, create two new layers, one named bar and
the other text fields.
At frame 4 of the bar and text fields layers, add a
blank keyframe.
At frame 15 of the bar and text fields fields layers,
add a blank keyframe.
Now we'll create the preload bar which is composed of two movie clips:
Create a movie clip symbol named loadBarHousing.
In loadBarHousing, create an 8-pixel high, 140-pixel wide
rectangularly shaped outline. (Make sure the rectangle has no fill.)
Select the rectangle in loadBarHousing, then choose
Window-->Panels-->Info.
Position the rectangle's left edge on the clip's registration point by setting its X-coordinate to 0 and its Y-coordinate to -4.
Create a movie clip symbol named loadBar.
In loadBar, create an 8-pixel high, 1-pixel wide solid
rectangle. (Make sure the rectangle has no outline).
Select the rectangle in loadBar, then choose
Window-->Panels-->Info.
Position the rectangle's left edge on the clip's registration point by setting its X-coordinate to 0 and its Y-coordinate to -4.
The preload bar clips are done; now place them on the Stage:
At frame 4 of the bar layer, place an instance of
loadBarHousing.
Name the instance loadBarHousing
(Modify-->Instance).
At frame 4 of the bar layer, place an instance of
loadBar.
Name the instance loadBar.
Select the loadBarHousing and loadBar
instances.
On the Align panel (Window-->Panels-->Align) select the Align Left Edge button (top-left button on the panel) and the Align Bottom Edge button (top-right button on the panel).
Finally, create the text fields to hold the kilobyte and percentage information for the preloader:
Select the Text tool.
At frame 4 of the text fields layer, drag out a text box big
enough to hold a three-digit number.
Choose Text-->Options.
In the Text Options panel, change Static Text to Dynamic Text.
In the Text Options panel, select the Include Entire Font Outline option under Embed Fonts (the [...] button). (Note that in a production situation you should only embed the characters you actually use in your text fields--in our case, numbers and the percent sign).
In the Variable text field, type bytesLoadedOutput.
Repeat steps 1 through 6 to create two more text fields, named
bytesTotalOutput and percentOutput.
Add regular static text next to each text field describing its content
as follows: Loaded: , Total: , Percent: .
Now the fun part--update the code on frame 5 so it reads as follows
(we're only changing the contents of the else clause, but the code
is shown in its entirety):
// Specify how many frames to load before playing.
var loadAmount = _totalframes;
// If the required number of frames have finished loading...
if (_framesloaded == loadAmount) {
// ...start the movie
gotoAndPlay("beginMovie");
} else {
// ...otherwise, display the load status
// then go back and check load progress again.
// First, determine the loaded and total kilobytes.
loaded = Math.round(getBytesLoaded() / 1024);
total = Math.round(getBytesTotal() / 1024);
percent = Math.round((loaded/total) * 100);
// Display the loaded kb, total kb, and percentage of
// kb loaded in text fields.
bytesLoadedOutput = loaded;
bytesTotalOutput = total;
percentOutput = percent + "%";
// Set the width of the loadBar to a percentage of
// the loadBarHousing.
loadBar._width = loadBarHousing._width * (percent / 100);
// Now go back and check load progress.
gotoAndPlay("loading");
}
Use the Bandwidth Profiler to test your preloader. The more content you have on your timeline, the more you'll see the preload bar working.
Let's examine the changes to our code on frame 5. As before, we'll only
begin the movie if the number of frames loaded matches our required
loadAmount:
if (_framesloaded == loadAmount)
But this time, if the required number of frames hasn't loaded yet, we'll
update our on-screen text fields and preload bar before sending the playhead
back to the loading frame. First we calculate how many kilobytes
have loaded. The getBytesLoaded() movie clip method tells us how
many bytes have loaded; we divide its return value by 1024 to convert to
kilobytes. To keep our output readable, we round off the result using
Math.round():
loaded = Math.round(getBytesLoaded() / 1024);
Next we calculate how many kilobytes are in the entire movie. We use the
getBytesTotal() method to determine the movie's size in bytes.
Dividing by 1024 and rounding once again gives us kilobytes:
total = Math.round(getBytesTotal() / 1024);
To compute what percent of the movie had downloaded, we divide
loaded by total and multiply the result by 100. Using
Math.round(), we round the percent to the nearest integer for
display:
percent = Math.round((loaded/total) * 100);
To display our calculated values on screen, we assign loaded,
total, and percent to corresponding text fields:
bytesLoadedOutput = loaded;
bytesTotalOutput = total;
percentOutput = percent + "%"; // Add a % sign for display purposes.
Finally, we set the width of our preload bar. We know that loadBar
should be as wide as loadBarHousing when our movie is completely
loaded. While our movie is still loading, loadBar's width should be
some percentage of loadBarHousing's width. We've already calculated
the percent of the movie that has loaded, so now we simply set
loadBar's _width property to the appropriate percent of
loadBarHousing's _width property, in accordance with our
percent variable:
loadBar._width = loadBarHousing._width * (percent / 100);
With our display updated, we send the movie back to the loading
frame, where it will play and execute our preload check again:
gotoAndPlay("loading");
Reader Exercise: Ideally, while our movie loads we should distract the user with eye candy, a prelude to a story-line, a game, or some other toy. See if you can add these things by creating an entertaining movie clip placed on frame 4. Be careful to make your distraction small enough to load quickly. If it's too big, you'll have to preload your preloader!
Using loadMovie() we can load external .swf files into
movie clips (called targets) or document levels. Externally loaded
.swf files can be preloaded exactly like single Flash movies. For
example, consider a Flash site with a main menu that links to three
sections: Products, Contact, and About Us. Each section resides in a
separate .swf file: products.swf, contact.swf, and
aboutus.swf. The site's homepage, menu.swf, provides
buttons that load each section into level 1, as follows:
on (release) {
loadMovie("products.swf", "_level1");
}
To preload, say, the Products section, we place a single-movie preloader
directly on the main timeline of products.swf. When
products.swf is loaded onto level 1, its preloader will manage the
download.
There are times, however, when this approach is inefficient. For example,
suppose we're building a music player that loads multiple soundtrack
.swf files into a single target instance named host. Rather
than create a separate preloader for every .swf file, we create a
single, generic preloader that manages the download of any file loaded into
host.
Let's see how this works. We start with a series of buttons that load our
soundtrack movies into the host clip. The code on our first button
looks like this:
on (release) {
host.loadMovie("soundtrack1.swf");
}
When soundtrack1.swf loads into host, we want
host to automatically preload the file. We need to add a little
intelligence to the host clip. When a movie is loading into the
host clip it should call a custom preload() function to
handle the download. We'll use the following enterFrame movie clip
event handler to monitor the file in host and to call
_root.preload() when necessary:
onClipEvent (enterFrame) {
// If the host clip contains an external .swf file...
if (this._url != _root._url) {
// ...call our preload() function, which displays the
// loading .swf file's download progress.
_root.preload(this);
}
}
As each frame passes, the code in host's enterFrame
handler is executed. If host's filename (this._url) does not
match the filename of the main music player (_root._url), then an
external movie must be in host, so we should run our
preload() function. The preload() function will display load
progress and optionally determine when to play the movie in host.
Before we look at how preload() works, it bears mentioning that the
enterFrame handler just shown is not the optimal handler to use in
this situation. Ideally, we'd call preload() from a data event,
which only executes when data loads into a movie clip. Unfortunately I've
found that the data event doesn't always fire on fast connections (DSL,
Cable, T1). This is an unverified bug, but it was problematic enough to
warrant moving the preload() call to an enterFrame handler.
I've reported my findings to Macromedia and will post any resolutions on
my Web site.
Now, back to the preload() function. Skim through it to get a sense
of how it works, then we'll dissect it.
function preload (theClip) {
if (!theClip.doneLoading) {
// If we have all the frames, make a
// note that download is complete.
if (theClip._framesloaded > 0
&& theClip._framesloaded == theClip._totalframes) {
theClip.doneLoading = true;
// Optionally start the clip once it's done loading...
// theClip.play();
} else {
// Optionally pause the clip until it's loaded...
// theClip.stop();
}
// Display loading byte counts in text fields.
bytesLoadedOutput = theClip.getBytesLoaded();
bytesTotalOutput = theClip.getBytesTotal();
// Strip out the file name of the .swf loading into the
// clip and display it in a text field.
var lastSlash = theClip._url.lastIndexOf("/");
clipURLOutput = theClip._url.substring(lastSlash + 1,
theClip._url.length);
// Set the width of the loading bar.
var percentLoaded = (theClip.getBytesLoaded()
/ theClip.getBytesTotal());
preloadBar._width = preloadBarBG._width * percentLoaded;
}
}
[Editor's note: Lines 20 and 22 were extended to two lines each to accommodate our Web site's formatting. Throughout this article, whenever a line of code has been broken up into two lines, the second line is indented.]
The preload() function takes one parameter, theClip, which
is a reference to the clip to preload (allowing for the potential of more
than one host clip):
function preload (theClip) {
Recall that our preload() function is called once per frame for as
long as an external .swf file resides in host. It's a waste
of time to execute any preloading code if that file is fully loaded, so
preload()'s first job is to check whether or not theClip
has finished loading:
if (!theClip.doneLoading) {
The variable doneLoading will be set in theClip when
loading completes; if it doesn't exist, then we need to run our preloading
code. There's not much new in the preloading code itself. We start by
checking whether the number of frames loaded so far equals the movie's total
number of frames, but we also make sure that there's at least one frame
loaded.
if (theClip._framesloaded > 0
&& theClip._framesloaded == theClip._totalframes) {
The _framesloaded > 0 safeguard is necessary when working with
movies loaded into target clips because unloading a clip's contents sets its
_totalframes to 0. Hence, on a very slow connection the comparison
_framesloaded == _totalframes can return true even when no
frames have yet loaded (because _totalframes and
_framesloaded would both be zero).
When we find that all the frames in theClip have loaded, we set a
flag indicating that we no longer need to run our preloading code.
theClip.doneLoading = true;
At this point we could also play the movie (knowing that it has safely loaded) by simply executing:
theClip.play();
However, in our music player example, we'll let the movie start playing
immediately as soon as it loads, much like an MP3 player, so there's no need
to invoke theClip.play().
If all the frames of theClip have not yet loaded, then we might
stop it from playing prematurely as follows:
} else {
theClip.stop();
}
But again, in our example there's no need to stop the movie; we let it play while the music streams to the Flash player. In any event, we'll always want to update the progress display text fields and preload bar. This time we'll display raw bytes rather than converting to kilobytes as we did in earlier examples:
// Display loading byte counts in text fields.
bytesLoadedOutput = theClip.getBytesLoaded();
bytesTotalOutput = theClip.getBytesTotal();
// Strip out the file name of the .swf loading into the
// clip and display it in a text field.
var lastSlash = theClip._url.lastIndexOf("/");
clipURLOutput = theClip._url.substring(lastSlash + 1,
theClip._url.length);
// Set the width of the loading bar.
var percentLoaded = (theClip.getBytesLoaded()
/ theClip.getBytesTotal());
preloadBar._width = preloadBarBG._width * percentLoaded;
Notice that we display the filename of the loading movie by extracting
everything after the last slash in theClip._url. That's just one of
the endless ways to customize a preloader. You might also fade a clip in as a
movie loads by setting its _alpha property, or increase the volume
of a sound, or calculate how long a file is taking to transfer and estimate
the time remaining. In the source file for our music player example, we
indicate not only how much of the movie loaded, but also how much of it has
played.
Now that we've built a preloader for movies loaded into a target clip, we
can easily use it to load attached sounds. An attached sound is a sound added
dynamically to a movie at runtime. To attach a sound we first export it from
the Library (under Options-->Linkage), then we use an instance of the
built-in Sound class to create, attach, and play the sound.
For example, the following code adds a sound with the Symbol Linkage
Identifier loudBang to the Sound object bang. The
code then starts and stops the bang sound.
// Create a new Sound object scoped to the _root timeline.
bang = new Sound(_root);
// Retrieve the loudBang sound from
// the library and attach it to bang.
bang.attachSound("loudBang");
// Start the bang sound
bang.start();
// Stop just the bang sound
bang.stop("loudBang");
Any sound exported from a movie's library is downloaded in that movie's
first frame, causing a load delay before the movie starts. In order to avoid
the delay, we can place exported sounds in a separate .swf file that
we load into a target clip when needed (exactly like we loaded movies in our
music player example). For example, suppose we've stored our exported sounds
in linkedSounds.swf. We use our multiple-movie preloader to load
linkedSounds.swf into our host movie clip. When the file has
completely loaded we can safely attach and play its exported sounds as
follows:
// Create a new sound with a target of host.
host.bang = new Sound(host);
host.bang.attachSound("loudBang");
host.bang.start();
Notice that the sound being attached must be available in the Library of the
Sound object's specified target (in our case, host).
It's a common mistake to omit the target argument when constructing
Sound objects in loaded movies. If not supplied, target
defaults to _level0, where the sound cannot be found. For example,
the following attempt to play a sound in host would fail because the
sound loudBang is not available in the library of
_level0:
// Create a new sound with an implied target of _level0.
host.bang = new Sound();
// This operation fails..."loudBang" is not in _level10's Library.
host.bang.attachSound("loudBang");
// The sound won't start because it wasn't successfully attached.
// We forgot to specify a target when we constructed host.bang.
host.bang.start();
Loading XML and variables is thoroughly covered in the pages of ActionScript: The Definitive Guide, so I won't repeat myself here. The following executive summary should help you find the relevant information in either that book or Macromedia's documentation.
To preload an XML document, we use the onLoad() callback handler of
the XML class. The onLoad() handler automatically executes
when Flash finishes loading and parsing an XML document requested via
XML.load() or XML.sendAndLoad(). In a typical
implementation, we invoke XML.load(), then send the movie to a frame
with a loading message, then wait for the onXML() handler to tell us
it's safe to resume playback (e.g., process the data and display it).
To intercept the raw data loaded by an XML object, we can
alternatively use the undocumented onData() handler. It is
unfortunately not possible in Flash 5 to determine the load progress of an
XML document being retrieved. For more information see the entries for
XML.send(), XML.sendAndLoad(), XML.onData(), and
XML.onLoad() in the Language Reference of
ActionScript: The
Definitive Guide. (The relevant sample chapter is
posted on my Web site). See also Macromedia's Flash XML primer for more information.
For loaded variables, we also use an event handler to handle preloading. We
start by invoking the loadVariables() function, which imports
variables from a server side script (or text file) into a movie clip's
timeline. While we're waiting for the variables, we display a loading frame.
When the variables arrive, the clip's data event is triggered, and
we carry on with the movie. A common alternative (which is Flash 4 friendly)
to the data handler is to run repeated checks for the value of our
last loaded variable, and proceed only when that variable becomes defined. In
either case, we unfortunately can't determine the load progress of our
variables; we can only detect when they have fully loaded.
For a full step-by-step tutorial on loading variables, see Chapter 17 of ActionScript: The Definitive Guide. Happy preloading!
Colin Moock has been researching, designing, and developing for the Web since 1995. Colin served as Webmaster for SoftQuad Inc. (makers of HoTMetaL PRO) until 1997. He is now a Web evangelist for ICE Integrated Communications & Entertainment, where he divides his time between writing about the Web, speaking at conferences, and creating interactive content for companies like Sony, Levi's, Nortel, Air Canada, and Hewlett-Packard. Colin's award-winning Flash work and his renowned support site for Flash developers have made him a well-known personality in the Flash developer community. Macromedia has officially recognized his Flash expertise both on their Web site and by appointing him a member of their Flash Advisory Board. Colin is a contributing author of The Flash 4 Bible (1999, IDG Books) and The Flash 5 Bible (2001, IDG Books). He is the author of O'Reilly's ActionScript: The Definitive Guide. For more information on Colin Moock, visit his Web site.
|
Related Reading ActionScript: The Definitive Guide
Table of Contents
Index Sample Chapter Author's Article Read Online--Safari Search this book on Safari: |
Copyright © 2007 O'Reilly Media, Inc.