1. Code
  2. JavaScript

Blow an Image Away with a Custom Wind Effect

Scroll to top
15 min read
This post is part of a series called GreenSock Tweening Platform.
Interview with Jack Doyle, Founder of GreenSock
Create an Organic Dissolve With Perlin Noise

Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Activetuts+. This tutorial was first published in March, 2010.

In this tutorial, we will create a custom class which breaks a picture into a thousand pieces and simulates a wind blowing them away. I created this project purely with AS3 and FlashDevelop - Flash not required!


Final Result Preview

Let's take a look at the final result we will be working towards. Go ahead and click anywhere inside the SWF:


Quick Intro to FlashDevelop

FlashDevelop is a free code editor for Flash and Flex. You can use it to edit your class files when working with Flash software, or you can create an AS3 Project that doesn't require Flash at all - and that's exactly what we'll be doing in this tutorial.

So download FlashDevelop and install it. Unfortunately, FlashDevelop only runs on Windows. Mac alternatives include FDT and Flex Builder, though neither are free. You can use Flash itself, and I'll explain how to do this as we go along.


Step 1: Create a New Project

Open FlashDevelop and click Project>New Project...


Step 2: Set Up

Choose Actionscript 3 > AS3 Project. For the name of the Project put in "WindEffect." For the location, click and navigate to the folder you would like to save it into. Leave the "Create Directory For Project" checkbox selected and click OK.

If you want to use Flash CS3/CS4, create a new Flash file and set the width and height of the stage to 550x250px, set the background color to black. Name it "windEffect.fla" and save it anywhere you like.


Step 3: Move the Source Image

For FlashDevelop, open the project directory and copy or drag windEffect.jpg from the source download (linked at the top of the page) into the \bin\ folder.

For Flash, copy or drag windEffect.jpg from the source download into the same folder where you have windEffect.fla.


Step 4: Install TweenLite

We're going to use TweenLite by Greensock for the tweening. You can download the latest version of the component here; I've also included it in the source download.

For FlashDevelop, go ahead and copy or drag greensock.swc from the source download into the \lib\ folder for this project.

From FlashDevelop, click View>Project Manager


Step 5: External Library

Still in FlashDevelop, click the '+' sign to the left of the lib folder to expand it. Right-click greensock.swc and select Add To Library.

For Flash, copy or drag the \com\ folder from the source download into the same folder as your windEffect.fla file.


Step 6: The Document Class

For FlashDevelop, open the project manager again (refer to Step 4), expand the \src\ folder and double-click Main.as. Below the imports and right above the class definition, add the following metadata tag to set up the stage properties:

1
[SWF (width = 550, height = 250, frameRate = 30, backgroundColor = 0)]

Within the init () method after the comment 'entry point', add the following code:

1
2
stage.scaleMode = StageScaleMode.NO_SCALE;	//don't stretch the stage

3
var effect:WindEffect = new WindEffect('windEffect.jpg');	//we will create the WindEffect class soon

4
addChild (effect);

That's it for the Main document class.

For Flash, create a new Main.as class in the same folder as your project. Make sure the Main.as class is in the same folder as the fla. & com folder. Add the following lines:

1
package
2
{
3
	import flash.display.Sprite;
4
	import flash.display.StageScaleMode;
5
	import flash.events.Event;
6
7
	public class Main extends Sprite
8
	{
9
10
		public function Main():void
11
 		{
12
 			if (stage) init();
13
			else addEventListener(Event.ADDED_TO_STAGE, init);
14
 		}
15
16
		private function init(e:Event = null):void
17
 		{
18
 			removeEventListener(Event.ADDED_TO_STAGE, init);
19
 			stage.scaleMode = StageScaleMode.NO_SCALE;	//don't stretch the stage

20
21
 			var effect:WindEffect = new WindEffect ('windEffect.jpg');	//we will create the WindEffect class soon

22
 			addChild (effect);
23
 		}
24
 	 }
25
}

Open Flash and assign "Main" as the Document class.

(Not sure what this is all about? Read this quick introduction to using a document class.)

If you try to run this now, you will get an error since we haven't created the WindEffect class yet. Just make sure you save the file and leave it for now.


Step 7: Create the WindEffect Class

For FlashDevelop, click View>Project Manager, right-click the \src\ folder and choose Add>New Class.


Step 8: Setting Up the Class

Name the class WindEffect, click the browse button for the base class and enter flash.display.Sprite. Hit OK to complete.


Step 9: Importing Other Classes

Add all the necessary imports inside the package brackets right below 'import flash.display.Sprite;' and before the class definition. Click Save.

1
import com.greensock.easing.Strong;
2
import com.greensock.TweenLite;
3
import flash.display.Bitmap;
4
import flash.display.BitmapData;
5
import flash.display.Loader;
6
import flash.events.Event;
7
import flash.events.MouseEvent;
8
import flash.geom.Point;
9
import flash.geom.Rectangle;
10
import flash.net.URLRequest;

For Flash, create a new ActionScript file, name it "WindEffect.as" and save it in the same directory you have been using. It should be right next to the fla. file, com folder, and Main.as.

Add the following code:

1
package
2
{
3
	import com.greensock.easing.Strong;
4
	import com.greensock.TweenLite;
5
	import flash.display.Bitmap;
6
	import flash.display.BitmapData;
7
	import flash.display.Loader;
8
	import flash.display.Sprite;
9
	import flash.events.Event;
10
	import flash.events.MouseEvent;
11
	import flash.geom.Point;
12
	import flash.geom.Rectangle;
13
	import flash.net.URLRequest;
14
15
	public class WindEffect extends Sprite
16
	{
17
		public function WindEffect ()
18
		{
19
20
		}
21
22
	}
23
24
}

Step 10: Add an Instance Variable

Add a private variable called "_pictureArray." This is the only variable we will have in this class. Its main purpose is to hold references to all the small sprites that contain the little pieces of the picture once it's been broken up.

Add the following line of code within the brackets
of the class:

1
public class WindEffect extends Sprite
2
{
3
	//this will house all the pieces of the picture we will animate.

4
	private var _pictureArray:Array;
5
}

Step 11: Add the Constructor

Add the following lines after the _pictureArray declaration:

1
public class WindEffect extends Sprite
2
{
3
	//this will house all the pieces of the picture we will animate.

4
	private var _pictureArray:Array;
5
6
	public function WindEffect ($url:String)
7
	{
8
		//we just call the load picture in the constructor

9
		loadPicture ($url);
10
	}
11
}

Step 12: Access the Image

Inside the loadPicture () method called by the constructor method, we instantiate a loader to load the windEffect.jpg. We also add a COMPLETE event listener to listen for when the load completes.

Add the following lines of code after the WindEffect () method. (Note that the parameter "$url" is the path to the picture we are loading passed from Main.as.)

1
private function loadPicture ($url:String):void
2
{
3
	//we create a loader with listeners to load the source picture we are using.

4
	//and then we load the image.

5
	var loader:Loader = new Loader;
6
	loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);		//when it's loaded, call the onLoadComplete () function

7
	loader.load (new URLRequest ($url));
8
}

Step 13: Loading

After the image has been imported properly, this method is called. Add the following lines of code after the loadPicture () method and save the file.

1
private function onLoadComplete (e:Event):void
2
{
3
	//for testing

4
	addChild (e.target.content);
5
}

Step 14: Test One

Go ahead and hit CTRL + Enter on you keyboard. It should work and the image should be on the left top corner of the stage.

Now that we've checked it's loading correctly, remove the addChild method and replace it with the following code:

1
createEffect (e.target.content);

Your WindEffect class should look something like this:

1
package
2
{
3
	import com.greensock.easing.Strong;
4
	import com.greensock.TweenLite;
5
	import flash.display.Bitmap;
6
	import flash.display.BitmapData;
7
	import flash.display.Loader;
8
	import flash.display.Sprite;
9
	import flash.events.Event;
10
	import flash.events.MouseEvent;
11
	import flash.geom.Point;
12
	import flash.geom.Rectangle;
13
	import flash.net.URLRequest;
14
15
	[SWF (width = 550, height = 250, frameRate = 30, backgroundColor = 0)]
16
17
	public class WindEffect extends Sprite
18
	{
19
		private var _pictureArray:Array;
20
21
		public function WindEffect ($url:String)
22
		{
23
			loadPicture ($url);
24
		}
1
		private function loadPicture ($url:String):void
2
		{
3
			var loader:Loader = new Loader;
4
			loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);
5
			loader.load (new URLRequest ($url));
6
		}
7
8
		private function onLoadComplete (e:Event):void
9
		{
10
			createEffect (e.target.content);
11
		}
12
13
	}
14
15
}

Step 15: Set up the Variables

The createEffect () Method will take the image parameter which is essentially a bitmap and break it down to 1250 pieces.

First we calculate the x- and y-positions to center the image on the stage. We save them into local variables called centerWidth and centerHeight.

Since the size of the image we are using is 300x100, I decided to divide the image 50 times horizontally and 25 times vertically. These values gave a pretty decent result with optimum performance. We save them in local variables, which I named "numberOfColumns" and "numberOfRows."

We save the result of dividing the image's width by numberOfColumns into "sizeWidth" and the result of dividing the image's height by numberOfRows into "sizeHeight."

The "numberOfBoxes" variable holds numberOfColumns multiplied by numberOfRows.

Next we instantiate _pictureArray so we can start putting small sprites into it. Add the following lines of code after the onLoadComplete () method:

1
private function createEffect ($bitmap:Bitmap):void
2
{
3
	//center the image horizontally.

4
	var centerWidth:Number = (stage.stageWidth - $bitmap.width) * .5;
5
6
	//center the image vertically.

7
	var centerHeight:Number = (stage.stageHeight - $bitmap.height) * .5;
8
9
	var numberOfColumns:uint = 50;
10
	var numberOfRows:uint = 25;
11
	var sizeWidth:uint = $bitmap.width / numberOfColumns;
12
	var sizeHeight:uint = $bitmap.height / numberOfRows;
13
	var numberOfBoxes:uint = numberOfColumns * numberOfRows;
14
	_pictureArray = [];
15
16
}

Step 16: Nested Loops

After instantiating _pictureArray we will add two loops, one inside the other. The first loop will handle moving on the x-position and will loop through all the columns, while the second loop will move on the y-position and will loop through all the rows.

Add the following lines of code inside the createEffect () method right after instantiating _pictureArray, then save the file:

1
for (var i:uint = 0; i < numberOfColumns; i++)
2
{
3
	//these loops are what splits the image into 1250 pieces.

4
	for (var j:uint = 0; j < numberOfRows; j++)
5
	{
6
		//let's see what it does.

7
		trace ('i:' + i, 'j:' + j);
8
	}
9
}

Step 17: Test Two

Test the movie by hitting CTRL + Enter.

As you can see, for every i there is a full loop of j. This is called "nesting loops". This means that i which represents the x axis stays on one value while the second loop iterates for the y axis.

Put simply, we start with x=0, y=0; then the next iteration is x=0, y=1; then x=0, y=2, and so on.

When y reaches the end, the first loop increments by 1 and then again goes through the 2nd loop: x=1, y=0; x=1, y=1, x=1, y=2, etc. This goes on until the 1st loop completes.

You'll see what this does when we apply it to some bitmap manipulation in the next few lines.


Step 18: Splitting the Image

From within the second loop, go ahead and remove the trace function we used for testing. Every time we loop we need to create a small picture having the width of "sizeWidth" and the height of "sizeHeight."

This small picture will take a snap shot of a small part of the image starting from the top-left corner and moving through to the bottom-right. The "tempBitmapData" is where we will draw the small portion of the image. The "sourceRect" is the rectangle we will use to specify which part of the image will be copied.

Add the following lines inside the 2nd loop and save the file:

1
//1 temporary bitmapdata

2
var tempBitmapData:BitmapData = new BitmapData (sizeWidth, sizeHeight);
3
4
//1 temporary rectangle (x,y,width,height)

5
//we pass i * sizeWidth for the x parameter  & i * sizeHeight for the y parameter

6
//and the sizeWidth & sizeHeight for the width and height parameters.

7
var sourceRect:Rectangle = new Rectangle (i * sizeWidth, j * sizeHeight, sizeWidth, sizeHeight);
8
trace (sourceRect);//for testing

Step 19: Even More Testing

Test the movie. What it does now is create a rectangle that adjusts its x- and y-positions each iteration.

As you can see, the 1st example shows x=0, y=0 and the next is x=0, y=4. This is what what we use for boundaries of the snap shot taken from the source image. Remove the test function when you're ready to move on.


Step 20: BitmapData.copyPixels()

We then use the BitmapData.copyPixels () method to copy a small piece of the image based on the sourceRect. The parameters for this method are the bitmap image to copy, the rectangle area to copy and the destination point to where we will copy it.

Add the following line of code below the sourceRect declaration.

1
tempBitmapData.copyPixels ($bitmap.bitmapData, sourceRect, new Point);

We then create one temporary Bitmap to house the BitmapData we just copied and one temporary Sprite to house that Bitmap.

Then we push a reference of each Sprite onto _pictureArray for access later. After this we add the Sprite to the stage with the same coordinate as where we copied it from, thus recreating the original image.

We then offset the image by centerWidth and centerHeight to center it correctly on the stage.

Add the following lines of code and, once again, save the file:

1
//we then create 1 temporary bitmap to house the bitmapdata we just copied.

2
var tempBitmap:Bitmap = new Bitmap (tempBitmapData);
3
4
//and 1 temporary sprite to house the bitmap to enable interactivity.

5
var tempSprite:Sprite = new Sprite;
6
7
//we just add each box inside it's own sprite to enable interactivity since bitmaps by themselves are not interactive.

8
tempSprite.addChild (tempBitmap);
9
10
//each sprite is added into the _pictureArray array for access later.

11
_pictureArray.push (tempSprite);
12
13
//then position each of them onto the stage. 

14
//We add the center width & center height so image centers on the stage.

15
tempSprite.x = i * sizeWidth + centerWidth;
16
tempSprite.y = j * sizeHeight + centerHeight;
17
addChild (tempSprite);

Step 21: Test Three

Go ahead and test it again. You should see the image correctly laid out on the stage. It won't even look like it's been separated into 1250 pieces.

Right after the closing bracket of the second loop, before we close the method, add the following line of code:

1
stage.addEventListener (MouseEvent.CLICK, blowWind);

We add an event listener to the stage to listen for a MouseEvent.CLICK. This will trigger the animation by running the blowWind() function, which we'll create in the next step.

Your WindEffect class should look something like this:

1
package
2
{
3
	import com.greensock.easing.Strong;
4
	import com.greensock.TweenLite;
5
	import flash.display.Bitmap;
6
	import flash.display.BitmapData;
7
	import flash.display.Loader;
8
	import flash.display.Sprite;
9
	import flash.events.Event;
10
	import flash.events.MouseEvent;
11
	import flash.geom.Point;
12
	import flash.geom.Rectangle;
13
	import flash.net.URLRequest;
14
15
	public class WindEffect extends Sprite
16
	{
17
		private var _pictureArray:Array;
18
	
19
		public function WindEffect ($url:String)
20
		{
21
			loadPicture ($url);
22
		}
23
		private function loadPicture ($url:String):void
24
		{
25
			var loader:Loader = new Loader;
26
			loader.contentLoaderInfo.addEventListener (Event.COMPLETE, onLoadComplete);
27
			loader.load (new URLRequest ($url));
28
		}
29
30
		private function onLoadComplete (e:Event):void
31
		{
32
			createEffect (e.target.content);
33
		}
34
35
		private function createEffect ($bitmap:Bitmap):void
36
		{
37
			var centerWidth:Number = (stage.stageWidth - $bitmap.width) * .5;
38
			var centerHeight:Number = (stage.stageHeight - $bitmap.height) * .5;
39
40
			var numberOfColumns:uint = 50;
41
			var numberOfRows:uint = 25;
42
			var sizeWidth:uint = $bitmap.width / numberOfColumns;
43
			var sizeHeight:uint = $bitmap.height / numberOfRows;
44
			var numberOfBoxes:uint = numberOfColumns * numberOfRows;
45
			_pictureArray = [];
46
47
			for (var i:uint = 0; i < numberOfColumns; i++)
48
			{
49
				for (var j:uint = 0; j < numberOfRows; j++)
50
				{
51
					var tempBitmapData:BitmapData = new BitmapData (sizeWidth, sizeHeight);
52
53
					var sourceRect:Rectangle = new Rectangle (i * sizeWidth, j * sizeHeight, sizeWidth, sizeHeight);
54
	
55
					tempBitmapData.copyPixels ($bitmap.bitmapData, sourceRect, new Point);
56
					var tempBitmap:Bitmap = new Bitmap (tempBitmapData);
57
					var tempSprite:Sprite = new Sprite;
58
					tempSprite.addChild (tempBitmap);
59
					_pictureArray.push (tempSprite);
60
					tempSprite.x = i * sizeWidth + centerWidth;
61
					tempSprite.y = j * sizeHeight + centerHeight;
62
					addChild (tempSprite);
63
				}
64
			}
65
			stage.addEventListener (MouseEvent.CLICK, blowWind);
66
		}
67
	}
68
}

Step 22: Creating the Wind Effect

Start off by removing the MouseEvent.CLICK event listener since we need it to happen only once. Add the following lines of code after the createEffect () method:

1
private function blowWind (e:MouseEvent):void 
2
{
3
	stage.removeEventListener (MouseEvent.CLICK, blowWind);
4
}

We need to go through all the sprites we assigned into _pictureArray and animate them individually.

TweenLite is applied to animate all the pieces towards the right as if wind were blowing on them.

The parameters are: the target for the tween, the duration of the tween, a variable object that holds all the properties, along with the values you want to apply the tween to.

For example: TweenLite.to (target, duration, {x:100, y:100, rotation:30, ease:Strong.easeIn, onComplete:trace, onCompleteParams:['hello']}).

The above example's last two parameters are used for when the tween finishes. The onComplete parameter calls the trace function and the onCompleteParams parameter sends an array containing the string 'hello' into the trace function.

Add the following lines of code right after the remove event listener:

1
for (var i:uint = 0; i < _pictureArray.length; i++)
2
{
3
	TweenLite.to (
4
	_pictureArray[i],
5
	getRandomInRange (.25, 2, false),
6
	{
7
		x: stage.stageWidth + 100,
8
		y:_pictureArray[i].y + getRandomInRange (-100, 100, false),//

9
		rotation: getRandomInRange (-90, 90),
10
		ease:Strong.easeIn,
11
		onComplete:removeSprite,
12
		onCompleteParams:[_pictureArray[i]]
13
	}
14
	);
15
}

In the actual implementation, when we call TweenLite from within the loop, we assign the target as _pictureArray[current iteration].

For the duration we assign a value for the length of the tween to a random time between .25 seconds and 2 seconds.

The variable object holds 5 properties:

  • x:stage.stageWidth + 100 which will animate the sprite's x property.
  • y:_pictureArray[i].y + getRandomRange (-100,100,false) which will get the current sprite's y position and add a random number between -100 and 100 to give the animation an expanding effect.
  • rotation:getRandomRange (-90,90) rotates the current sprite to anywhere between -90 and 90 degrees.
  • ease:Strong.easeIn which makes the tween start slowly and suddenly speed up.
  • onComplete:removeSprite which calls the removeSprite method once the tween has finished and the Sprite is off the screen.
  • onCompleteParams which sends the array [_pictureArray[current iteration]] as the parameter for removeSprite.

Step 23: removeSprite () Method

This method is called from TweenLite when the animation for a particular tween has finished. We just remove the Sprite from the display list so there's no clutter. Add the following lines of code after the blowWind () method:

1
private function removeSprite ($sprite:Sprite):void
2
{
3
	removeChild ($sprite);
4
}

Step 24: getRandomInRange () Method

I'm sure you're familiar with this one (if not, Carlos Yanez has written a Quick Tip on the subject.) My version has an option of returning either whole numbers (int, uint) or floats (fractions).

Add the following lines of code. If you're using FlashDevelop, you can save it as a custom snippet so it's easily added to any class/project. I have it declared as a public static method for full accessibility.

1
public static function getRandomInRange ($min:Number, $max:Number, $rounded:Boolean = true):Number
2
{
3
	if ($rounded) return Math.round (Math.random () * ($max - $min) + $min);
4
	else return Math.random () * ($max - $min) + $min;
5
}

That's it! Run the movie. If there's anything wrong, check your code against the WindEffect class I've included in the source download.


Conclusion

The key to creating cool effects is to learn and master both image manipulation and animation tweening classes like TweenLite. Please feel free to drop a note for any comments, concerns, or suggestions. Thanks for reading!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.