23 July 2007
This article is intended for Flash developers who have an intermediate to advanced understanding of ActionScript 2.0 or 3.0 and also of ActionScript classes.
Intermediate
You're probably aware of those nice reflections appearing in some of the latest websites, online ads, and even software. It's an effect common to "Web 2.0" designs, in which something like an album cover or video player appears to reflect some sort of virtual floor beneath it. This tutorial steps you through the creation of the Reflect class, a custom ActionScript 3.0 class that you can apply to reflections on movie clips (and use to modify them) in your Flash CS3 Professional projects.
The Reflect class is intended for any developer or designer who would like to achieve an effect that gives a great impact to your design, but with little effort. Typically it would take a fair amount of effort to manually create reflections in your Flash files. This task would likely involve copying and pasting a clip and then modifying it to achieve the look of the reflection. Every time a change was made, you would need to update your effect. If you had an animated clip or video that you wanted to reflect, it might become completely unfeasible or even impossible. However, the Reflect class allows you to apply the reflection only once because it dynamically updates itself as a true mirror of the clip which it is reflecting.
My work on this ActionScript 3.0 class is a testament to my own love of the Flash community, which is unlike any other pool of developers or designers I know. If you ever need any help with code or features, they are willing to help. After my original code for reflecting movie clips in ActionScript 2.0 was released on my blog, many gracious members of the Flash community posted helpful comments, feature requests, and even code snippets to help improve the class. The updated class described in this article takes into consideration these additions from users who dedicated their time and effort to help me improve it.
Before we get started, it might be useful to demonstrate the effect I am describing. As you drag the sliders in the Reflection Explorer (see Figure 1), you will notice that the Reflection Code box dynamically updates and provides you with the code necessary for applying the reflection to the Flash file. This example, which requires Adobe Flash Player 9 (version 9,0,45,0 or later), assumes a movie clip with an instance name of ref_mc.
Figure 1. Reflection Explorer (drag the sliders to see the effect)
Click the Show Video button at the top of the Reflection Explorer to toggle between a video and its image-based reflection. You may notice that when you are applying the Reflect class to the image, you are setting an update time of –1. This saves on processor overhead because you don't need to constantly update the reflection, as you would with the video. I will cover all of these parameters later in this article.
Whenever you start a class file, you should set the package library. The following line defines the package library path for the Reflect class:
package com.pixelfumes.reflect
For practical purposes, I named this file Reflect.as and nested it within a specific folder structure. This helps ensure that my code does not conflict with any other existing Reflect class code library that exists. For my purposes, I nested the class in the package path of com.pixelfumes.reflect. You really could make this whatever you want, but remember that the Reflect.as file needs to be in a folder structure that follows the package library. As you can see in the code download, the \com folder contains a \pixelfumes folder, which contains a \reflect folder in which the Reflect.as file lives.
The Reflect class takes advantage of a number of classes. As you follow through the code, you will see objects from these classes in use. First import the classes in order to make use of them within the Reflect class:
package com.pixelfumes.reflect{
import flash.display.MovieClip;
import flash.display.DisplayObject;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.geom.Matrix;
import flash.display.GradientType;
import flash.display.SpreadMethod;
import flash.utils.setInterval;
import flash.utils.clearInterval;
Now that you have the imported classes set, you can start writing the actual Reflect class. In this case, you are extending the MovieClip class. The following code opens the Reflect class and sets several variables that are used for tracking the information pertinent to the Reflect class:
public class Reflect extends MovieClip{
//Created By Ben Pritchard of Pixelfumes 2007
//Thanks to Mim, Jasper, Jason Merrill and all the others who
//have contributed to the improvement of this class
//static var for the version of this class
private static var VERSION:String = "4.0";
//reference to the movie clip we are reflecting
private var mc:MovieClip;
//the BitmapData object that will hold a visual copy of the mc
private var mcBMP:BitmapData;
//the BitmapData object that will hold the reflected image
private var reflectionBMP:Bitmap;
//the clip that will act as out gradient mask
private var gradientMask_mc:MovieClip;
//how often the reflection should update (if it is video or animated)
private var updateInt:Number;
//the size the reflection is allowed to reflect within
private var bounds:Object;
//the distance the reflection is vertically from the mc
private var distance:Number = 0;
Once you have the global variables for the class's scope defined, you can write the constructor function. The constructor function of the Reflect class accepts an object, which is referred to as args in the example below. The args object is responsible for passing the user-defined data into the Reflect class. For example, a typical instantiation of the Reflect class would look something like this within the Flash document:
import com.pixelfumes.reflect.*;
var r1:Reflect = new Reflect({mc:ref_mc, alpha:50, ratio:50,
distance:0, updateTime:0, reflectionDropoff:1});
Notice that to use the class, you have to import the Reflect class using the package's code library path already defined—namely, com.pixelfumes.reflect. After importing the class for use, define a variable to hold a reference to the Reflect class instance and pass it several items wrapped in an object. In this case, pass in the movie clip being reflected, the alpha for the reflection, the ratio to use in the gradient mask, the distance of the reflection, the update time, and the reflection dropoff.
The arguments are then pulled from the args object and used to set the class variables defined earlier. Based on the data passed into the class, you can set some additional variables within the class, such as the clip's height and width and the bounds in which the reflection can appear:
function Reflect(args:Object){
/*the args object passes in the following variables
/we set the values of our internal vars to math the args*/
//the clip being reflected
mc = args.mc;
//the alpha level of the reflection clip
var alpha:Number = args.alpha/100;
//the ratio opaque color used in the gradient mask
var ratio:Number = args.ratio;
//update time interval
var updateTime:Number = args.updateTime;
//the distance at which the reflection visually drops off
var reflectionDropoff:Number = args.reflectionDropoff;
//the distance the reflection starts from the bottom of the mc
var distance:Number = args.distance;
//store width and height of the clip
var mcHeight = mc.height;
var mcWidth = mc.width;
//store the bounds of the reflection
bounds = new Object();
bounds.width = mcWidth;
bounds.height = mcHeight;
Now that you have that out of the way, you can start getting into the meat of the class. The Reflection class makes heavy use of the BitmapData class, which enables you to take visual snapshots of movie clips with its draw method. Using the draw method, you can snapshot the movie clip passed into the class and use the data to populate a Bitmap object. Once you have the Bitmap object created, you can flip it upside down, position it, and add it to the Display Object stack. The following code handles the creation of the BitmapData and Bitmap objects, as well as handles their positioning:
//create the BitmapData that will hold a snapshot of the movie
clip
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
//create the BitmapData the will hold the reflection
reflectionBMP = new Bitmap(mcBMP);
//flip the reflection upside down
reflectionBMP.scaleY = -1;
//move the reflection to the bottom of the movie clip
reflectionBMP.y = (bounds.height*2) + distance;
//add the reflection to the movie clip's Display Stack
var reflectionBMPRef:DisplayObject = mc.addChild(reflectionBMP);
reflectionBMPRef.name = "reflectionBMP";
If you were to finish the class here by closing up the constructor and package, you would be presented with a movie clip that looks like Figure 2.
As you can see, the class is copying the visual representation of the movie clip, flipping it upside down and positioning itself under the original movie clip.
Now that you have the reflected image in the proper position, you can create a movie clip that holds the gradient that you'll use to create the gradient mask:
//add a blank movie clip to hold our gradient mask
var gradientMaskRef:DisplayObject = mc.addChild(new
MovieClip());
gradientMaskRef.name = "gradientMask_mc";
//get a reference to the movie clip - cast the DisplayObject
that is returned as a MovieClip
gradientMask_mc = mc.getChildByName("gradientMask_mc")
as MovieClip;
With the gradient mask clip added to the display, you can now work on the actual gradient. This gradient uses several values of interest. To create the gradient, you can use the alpha and ratio variables that were passed. These variables, along with the bounds I defined earlier, construct a gradient box within the gradientMask_mc clip.
The following code steps you through the creation of the linear gradient you will be using. Feel free to experiment with different fillType and spreadMethod instances, as these will alter the final appearance of your gradient mask:
//set the values for the gradient fill
var fillType:String = GradientType.LINEAR;
var colors:Array = [0xFFFFFF, 0xFFFFFF];
var alphas:Array = [alpha, 0];
var ratios:Array = [0, ratio];
var spreadMethod:String = SpreadMethod.PAD;
//create the Matrix and create the gradient box
var matr:Matrix = new Matrix();
//set the height of the Matrix used for the gradient mask
var matrixHeight:Number;
if (reflectionDropoff<=0) {
matrixHeight = bounds.height;
} else {
matrixHeight = bounds.height/reflectionDropoff;
}
matr.createGradientBox(bounds.width, matrixHeight,
(90/180)*Math.PI, 0, 0);
With the variables for the gradient mask now populated, you can now use the graphics property of the gradientMask_mc clip to create the GradientFill fill. Once the fill is created within the clip, position gradientMask_mc over reflectionBMP:
//create the gradient fill
gradientMask_mc.graphics.beginGradientFill(fillType, colors,
alphas, ratios, matr, spreadMethod);
gradientMask_mc.graphics.drawRect(0,0,bounds.width,bounds.height);
//position the mask over the reflection clip
gradientMask_mc.y = mc.getChildByName("reflectionBMP").y
- mc.getChildByName("reflectionBMP").height;
For the reflection to be properly masked with the gradient mask, you need to ensure that both the gradient mask and the reflectionBMP are set to cacheAsBitmap = true:
//cache clip as a bitmap so that the gradient mask will function
gradientMask_mc.cacheAsBitmap = true;
mc.getChildByName("reflectionBMP").cacheAsBitmap =
true;
//set the mask for the reflection as the gradient mask
mc.getChildByName("reflectionBMP").mask =
gradientMask_mc;
//cache clip as a bitmap so that the gradient mask will function
gradientMask_mc.cacheAsBitmap = true;
mc.getChildByName("reflectionBMP").cacheAsBitmap =
true;
//set the mask for the reflection as the gradient mask
mc.getChildByName("reflectionBMP").mask =
gradientMask_mc;
For the final part of the constructor, check to see if you have a valid update time, which refreshes the BitmapData that contains a snapshot of the movie clip you are reflecting. Clips that don't move and are not animated do not need an update time. If you pass in –1, you can avoid creating an interval to update the BitmapData object and save processing overhead. Movie clips containing videos or buttons that change upon rollover should have an update applied to them, so that the reflection remains visually accurate:
//if we are updating the reflection for a video or animation do
so here
if(updateTime > -1){
updateInt = setInterval(update, updateTime, mc);
}
With the bulk of the class out of the way, all you need now are a few methods to adjust the reflection. The setBounds method allows you to define an area in which the reflection is available. Keep in mind that, because you are taking a snapshot of the area defined by this method; larger bounds will use more memory. The setBounds method is ideal for clips that animate along a horizontal access within their own timeline.
Take a look at the animation in Figure 3 (click Start Demo to see the effect, Stop Demo to pause it). If a clip has the Reflect class applied to it at runtime, and then the clip's timeline moves items outside of the initial bounds of the clip, the Reflect class will not show them because it had its bounds defined by the size of the clip at runtime.
Figure 3. Animation using setBounds (click Start Demo to see the effect)
Using setBounds enables the user to compensate for the size difference by increasing the size of the bounds if needed:
public function setBounds(w:Number,h:Number):void{
//allows the user to set the area that the reflection is
allowed
//this is useful for clips that move within themselves
bounds.width = w;
bounds.height = h;
gradientMask_mc.width = bounds.width;
redrawBMP(mc);
}
The redrawBMP method, used by the setBounds class, serves the role of destroying the current BitmapData and creating a new one to compensate for the new bounds that were defined:
public function redrawBMP(mc:MovieClip):void {
// redraws the bitmap reflection - Mim Gamiet [2006]
mcBMP.dispose();
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
}
The update method refreshes the data held within the BitmapData snapshot of the movie clip. The more often the update method is run, the more processer-intensive the class becomes but the smoother the reflection playback looks. A good rule of thumb is to use as high a number as the visual performance will allow when setting the update time:
private function update(mc):void {
//updates the reflection to visually match the movie clip
mcBMP = new BitmapData(bounds.width, bounds.height, true,
0xFFFFFF);
mcBMP.draw(mc);
reflectionBMP.bitmapData = mcBMP;
}
You need a way to get rid of that reflection once you have created it. For this, you can create the destroy method, which removes the reflection items from the DisplayObject stack, clears the intervals, and removes all traces of the reflection:
public function destroy():void{
//provides a method to remove the reflection
mc.removeChild(mc.getChildByName("reflectionBMP"));
reflectionBMP = null;
mcBMP.dispose();
clearInterval(updateInt);
mc.removeChild(mc.getChildByName("gradientMask_mc"));
}
The Reflect class still has quite a few possibilities, and it leaves plenty of room for extension and improvement. Although using the Reflect class is pretty straightforward when working with static images, it becomes a bit different when reflecting items such as video. If you plan on reflecting a clip containing video, it is best to wait until the video is completely loaded before applying the class.
I would love to see any improvements and additions you would like to add to the Reflect class. Contact me at my blog, pixelfumes.com/blog. The more that we in the Flash community can push one another, the further we will push the boundaries of Flash as a platform.
| 04/23/2012 | Auto-Save and Auto-Recovery |
|---|---|
| 04/23/2012 | Open hyperlinks in new window/tab/pop-up ? |
| 04/21/2012 | PNG transparencies glitched |
| 04/01/2010 | Workaround for JSFL shape selection bug? |
| 02/13/2012 | Randomize an array |
|---|---|
| 02/11/2012 | How to create a Facebook fan page with Flash |
| 02/08/2012 | Digital Clock |
| 01/18/2012 | Recording webcam video & audio in a flv file on local drive |