Javascript Animation Example Using requestAnimFrame, Basic Trigonometry & HTML5 Canvas – Part 2

Once you have the original HTML5 Canvas example working, here’s a cleaner version of the code that eliminates global variables, adds a namespace and cleans up the html a bit.

The HTML file:

<!DOCTYPE HTML>
<html>
    <head>
        <style>
            body { margin: 0px; padding: 0px; }            
            #myCanvas { border: 1px solid #9C9898; }
        </style>
    </head>
    <body onmousedown="return false;">

        Click the below canvas to start the animation: <br />
        <canvas id="myCanvas" width="600" height="600"></canvas>

       <script>
            window.requestAnimFrame = (function(callback){
                return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(callback){
                    window.setTimeout(callback, 1000 / 60);
                };
            })();            
        </script>
        <script src="js/triganimation.js"></script> 

    </body>
</html>

The Javascript file

Make sure it’s located here: “js/triganimation.js”:

var TIMSHAYA   = TIMSHAYA || {}; //create your own namespace 

(function(whichCanvas){ 

   TIMSHAYA.Triganimation = new function() 
    {     
        var numsqr = 40;
        var mShapes = [];   
        var canvas = document.getElementById(whichCanvas);
        var context = canvas.getContext("2d");    

        function drawRect(myRect)
        {            
            context.beginPath();
            context.rect(myRect.x, myRect.y, myRect.width, myRect.height);
            
            context.fillStyle = "#8ED6FF";
            context.fill();
            context.lineWidth = myRect.borderWidth;
            context.strokeStyle = "black";
            context.stroke();
        }

        function animate(lastTime, mShapes, animProp)
        {
            if (animProp.animate) 
            {            
                var date = new Date();
                var time = date.getTime();
                var timeDiff = time - lastTime;

                for(var j = 0; j < numsqr; j++)
                {
                    mShapes[j].x = mShapes[j].centerX + Math.cos( mShapes[j].angle * Math.PI/180 ) * mShapes[j].radius;   
                    mShapes[j].y = mShapes[j].centerY + Math.sin( mShapes[j].angle * Math.PI/180 ) * mShapes[j].radius; 

                    (mShapes[j].angle < 360) ? mShapes[j].angle += 1 : mShapes[j].angle = 0;      
                }                            
                lastTime = time;
                
                // clear canvas
                context.clearRect(0, 0, canvas.width, canvas.height);
                
                // draw                    
                for(var l = 0; l < numsqr; l++)
                   drawRect(mShapes[l]);                    
                
                // request new frame
                requestAnimFrame(function(){
                    animate(lastTime,  mShapes, animProp);
                })
            }
        }

        this.init = function()
        {           
            //alert("this = " + this.name );
             for(var i = 0; i < numsqr; i++)
             {                     
                 mShapes[i] = {
                    angle:0,
                    width:20,
                    height:20,
                    x: 100 + i*10,
                    y: 250,
                    centerX: 290,
                    centerY: 290,      
                    radius: 250 - (1 + i * 5),            
                    borderWidth: 1
                }
             }
         
            var animProp = {
                animate: false
            }
          
            document.getElementById(whichCanvas).addEventListener("click", function(){
                if (animProp.animate) {
                    animProp.animate = false;
                }
                else {
                    animProp.animate = true;
                    var date = new Date();
                    var time = date.getTime();
                    animate(time,  mShapes, animProp);
                }
            });
          
            for(var k = 0; k < numsqr; k++)
               drawRect( mShapes[k] );                                   
        }
    } 
 
    TIMSHAYA.Triganimation.init();

})("myCanvas");
Advertisements

Notes on Optimizing SWF File Size

Some general notes on cutting file size of SWF files:

  • Where possible try writing your own basic animation functions avoid the overhead of entire libraries. Try switching from TweenMax to TweenLite or TweenNano. This should save you at least 3KB per level down.
  • Reduce the number of vector shapes. For example, if you have masked animation, especially a slow reveal – redraw the reveal part without vector shapes. Use 1 mc and change it’s position, add more layers, and duplicate the mc instead. Do whatever you can to avoid adding an extra vector shape to your file.
  • Reduced the number of Fonts used in your file. Try playing w/ alpha to fake font weight, in some cases this works to make a font appear less prominent.
  • Replace larger transparent PNGs with non-transparent GIFs/JPEGs and mask out the negative space. Balance this with the costs of adding another vector shape for the mask.
  • Optimize JPEG & PNG-8 assets in Photoshop for best results (not in the Flash IDE’s Library panel). PNG-24 can be further optimized in the Library, if needed.
  • Externalize your content (images, text, video, etc) where possible.

AS3.0 Signals example juxtaposed with native version

AS3.0 Signals is an event handling framework from Robert Penner that’s gaining popularity lately. There’s a good video tutorial by John Lindquist and tutorials here.

Here’s a quick comparison of an old XML processing class written both ways for comparison. For this simple example, Signals was about 10 to 50 milliseconds faster than using regular AS3 events. The results varied a lot each time I compiled.

Using the Signals framework for events:

XML processing class:

package com.timshaya.net
{
	import org.osflash.signals.Signal;
	import org.osflash.signals.natives.NativeSignal;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;	

	public class XmlGrabber 
	{		
		private var loadDone				:NativeSignal;									
		public var dataDone					:Signal;										
		public var cityItems				:Array = [];								

		private var xmlData					:XML;		
		private var loader					:URLLoader;
		private var reqst					:URLRequest;
		private var trgt					:URLLoader;
		
		public function XmlGrabber(pth:String):void 
		{
			loader = new URLLoader(); 
			reqst = new URLRequest(pth);
						
			dataDone = new Signal();	
			loadDone = new NativeSignal(loader, Event.COMPLETE, Event); 
			loadDone.addOnce(loadCompleted);					
			
			loader.load(reqst);	
		}	
		
		public function loadCompleted(e:Event):void 
		{
			var trgt:URLLoader = URLLoader(e.target);
			xmlData = new XML(trgt.data); 
			//trace(xmlData);
			
			if (trgt!=null) 
			{
				for each (var a:XML in xmlData.evnt) 
				{
					cityItems.push(a); 
					//trace("\n cityItems[a]: " + a.@city + "\n"); 
				} 													
			} else trace("error: check your data & datatype");
			
			dataReady();
		}
		
		public function dataReady():void 
		{	
			dataDone.dispatch(); 
		}
	}
}

Test class:

package 
{	
	import org.osflash.signals.Signal;	
	import org.osflash.signals.natives.NativeSignal;
	import com.timshaya.net.XmlGrabber; 	
	
	import flash.display.Sprite;	
	import flash.events.Event;	
	import flash.utils.getTimer;

	[SWF (width="600", height="400", backgroundColor="#FFFFFF", frameRate="30")]	
	public class Test extends Sprite 
	{	
		private var addToStage				:NativeSignal;
		private var xml						:XmlGrabber;		
		private var xmlPth                  :String = "xml/"; 
		private var xmlName  				:String = "events.xml"; 				
		private var timer					:Number;
		
		public function Test():void 		
		{										
			addToStage = new NativeSignal(this, Event.ADDED_TO_STAGE, Event); 
			addToStage.addOnce(initApp);			
		}

		public function initApp(e:Event):void 
		{
			timer = getTimer();
			
			xml = new XmlGrabber(xmlPth + xmlName);
			xml.dataDone.add(createPage); 									
		}

		public function createPage():void 
		{							
			trace("Test::createPage()");		
			trace("\t time elapsed: " + (getTimer() - timer));			
						
			trace("\t xml.cityItems[0].@city = " + xml.cityItems[0].@city); 
			trace("\t xml.cityItems[0].@state = " + xml.cityItems[0].@state);			
		} 				
	}
}

Same code using native AS3 events:

XML processing class:

package com.timshaya.net
{
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;	
	import flash.events.EventDispatcher;

	public class XmlGrabberOld extends EventDispatcher 
	{		
		public var cityItems				:Array = [];		

		private var xmlData					:XML;		
		private var loader					:URLLoader;
		private var reqst					:URLRequest;
		private var trgt					:URLLoader;

		public static var DATA_READY		:String	= "list_ready";
		
		public function XmlGrabberOld(pth:String):void 
		{
			loader = new URLLoader(); 
			reqst = new URLRequest(pth);
			loader.load(reqst);
			loader.addEventListener(Event.COMPLETE, listOnComplete, false, 0, true);	
		}
		
		private function listOnComplete(e:Event):void
		{			
			var trgt:URLLoader = URLLoader(e.target);
			xmlData = new XML(trgt.data);
			//trace(xmlData);
			
			if (trgt!=null) 
			{
				for each (var a:XML in xmlData.evnt) 
				{
					cityItems.push(a); 
					//trace("\n cityItems[a]: " + a.@city + "\n"); 
				} 					
			} else trace("error: check your data & datatype");			

			dataReady();
		}
		
		private function dataReady():void
		{
			dispatchEvent(new Event(XmlGrabberOld.DATA_READY));
		}
		
	}
}

Test class:

package 
{		
	import com.timshaya.net.XmlGrabberOld;

	import flash.display.Sprite;	
	import flash.events.Event;	
	import flash.utils.getTimer;

	[SWF (width="600", height="400", backgroundColor="#FFFFFF", frameRate="30")]	
	public class TestOld extends Sprite 
	{	
		private var xml						:XmlGrabberOld;		
		private var xmlPth                  :String = "xml/"; 
		private var xmlName  				:String = "events.xml"; 		
		private var timer					:Number;
		
		public function TestOld():void 		
		{								
			addEventListener(Event.ADDED_TO_STAGE, initApp);		
		}

		public function initApp(e:Event):void 
		{
			timer = getTimer();
		
			xml = new XmlGrabberOld(xmlPth + xmlName);
			xml.addEventListener(XmlGrabberOld.DATA_READY, createPage);											
		}

		public function createPage(e:Event):void 
		{						
			trace("TestOld::createPage()");		
			trace("\t time elapsed: " + (getTimer() - timer));		
			
			trace("\t xml.cityItems[0].@city = " + xml.cityItems[0].@city); 
			trace("\t xml.cityItems[0].@state = " + xml.cityItems[0].@state);						
		} 						
		
	}
}

Sample xml file, events.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<evnts>		
	
	<evnt lnk="test.aspx?c=0" city="Atlantic City" state="New Jersey" />
	<evnt lnk="test.aspx?c=1" city="Clifton" state="New Jersey" />	
	<evnt lnk="test.aspx?c=2" city="Allentown" state="Pennsylvania" />
	<evnt lnk="test.aspx?c=3" city="Akron" state="Ohio" />
	<evnt lnk="test.aspx?c=4" city="Wichita Falls" state="Texas" />
	<evnt lnk="test.aspx?c=5" city="Austin" state="Texas" />
	<evnt lnk="test.aspx?c=6" city="Orlando" state="Florida" />
	<evnt lnk="test.aspx?c=7" city="Atlanta" state="Georgia" />
	<evnt lnk="test.aspx?c=8" city="San Francisco" state="California" />
	<evnt lnk="test.aspx?c=9" city="Los Angeles" state="California" />
	<evnt lnk="test.aspx?c=10" city="San Diego" state="California" />	
	
</evnts>

AS3 scrollbar variation

Here’s a quick rewrite of a fun little basic scrollbar class written by Flashscaper.

The original was written pretty good and it was easy to drop into a project but there was one thing that would’ve made it more difficult for others to find out what’s happening in my code. The original Scrollbar.as was assigned directly to Scrollbar MovieClip via the Library’s Properties panel, rather than being instantiated through code. This technique works OK, but if some other developer had to pick up my code a year from now it would’ve been more difficult for her to find what’s where. To solve this issue, I rewrote the Scrollbar class so it can be started up via composition in Actionscript.

Made a few other small changes, like switching the animation code from Tweener to TweenLite. Had to move the stage listeners like this one:
“stage.addEventListener(MouseEvent.MOUSE_UP, stopScroll);”
to the “user” or client class called Main and use ScrollbarInstanceName.stopScroll to access public methods like stopScroll().

package com.flashscaper
{	
	import com.greensock.TweenLite; //TweenLite v.11.1
	import com.greensock.easing.Sine;	
	
	import flash.events.MouseEvent;	
	import flash.events.Event;	
	import flash.display.MovieClip;
	import flash.display.Sprite;	
	import flash.geom.Rectangle;

	public class BasicScrollbar extends MovieClip 
	{
		//data is "protected" instead of "private" so 
		//this class can be subclassed to add additional functionality 
		protected var myBar				:MovieClip; 		
		protected var target			:MovieClip;
		protected var top				:Number;
		protected var bottom			:Number;
		protected var dragBot			:Number;
		protected var range				:Number;
		protected var ratio				:Number;
		protected var sPos				:Number;
		protected var sRect				:Rectangle;
		protected var ctrl				:Number;
		protected var trans				:String;
		protected var timing			:Number;
		protected var isUp				:Boolean;
		protected var isDown			:Boolean;
		protected var isArrow			:Boolean;
		protected var arrowMove			:Number;
		protected var upArrowHt			:Number;
		protected var downArrowHt		:Number;
		protected var sBuffer			:Number;
		
		public function BasicScrollbar(t1:MovieClip, tr1:String, tt1:Number, sa1:Boolean, b1:Number) 
		{ 					 
			init(t1, tr1, tt1, sa1, b1); 
		}		
		
		public function	createBar():void 
		{			
			myBar = new Scrollbar_Lib_Asset(); //Library asset in the main .fla 					
			myBar.x = 201;
			myBar.y = 17;			
			addChild(myBar);
			
			myBar.scroller.addEventListener(MouseEvent.MOUSE_DOWN, dragScroll);					

		}		
		
		public function init(t:MovieClip, tr:String,tt:Number,sa:Boolean,b:Number):void {
			
			createBar();								
			
			target = t;
			trans = tr;
			timing = tt;
			isArrow = sa;
			sBuffer = b; 						

			if (target.height <= myBar.track.height) {
				this.visible = false;
			}			
						
			myBar.upArrow.buttonMode = true; //library & stage asset
			myBar.downArrow.buttonMode = true; //library & stage asset 
			
			upArrowHt = myBar.upArrow.height;
			downArrowHt = myBar.downArrow.height;
			if (isArrow) {
				top = myBar.scroller.y;
				dragBot = (myBar.scroller.y + myBar.track.height) - myBar.scroller.height;
				bottom = myBar.track.height - (myBar.scroller.height/sBuffer);

			} else {
				top = myBar.scroller.y;
				dragBot = (myBar.scroller.y + myBar.track.height) - myBar.scroller.height;
				bottom = myBar.track.height - (myBar.scroller.height/sBuffer);

				upArrowHt = 0;
				downArrowHt = 0;
				removeChild(myBar.upArrow);
				removeChild(myBar.downArrow);
			}
			range = bottom - top;
			sRect = new Rectangle(0,top,0,dragBot);
			ctrl = target.y;
			//set Mask
			isUp = false;
			isDown = false;
			arrowMove = 10;
			
			if (isArrow) {
				myBar.upArrow.addEventListener(Event.ENTER_FRAME, upArrowHandler);
				myBar.upArrow.addEventListener(MouseEvent.MOUSE_DOWN, upScroll);
				myBar.upArrow.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
				//
				myBar.downArrow.addEventListener(Event.ENTER_FRAME, downArrowHandler);
				myBar.downArrow.addEventListener(MouseEvent.MOUSE_DOWN, downScroll);
				myBar.downArrow.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
			}
			var square:Sprite = new Sprite();
			square.graphics.beginFill(0xFF0000);
			square.graphics.drawRect(target.x, target.y, target.width+5, (myBar.track.height+upArrowHt+downArrowHt));
			
			//trace("parent = " + parent); // = null			
			addChild(square); 
			
			target.mask = square;			
		}
		
		public function upScroll(event:MouseEvent):void 
		{
			isUp = true;
		}
		
		public function downScroll(event:MouseEvent):void 
		{
			isDown = true;
		}
		
		public function upArrowHandler(event:Event):void 
		{
			if (isUp) {
				if (myBar.scroller.y > top) {
					myBar.scroller.y -= arrowMove;
					if (myBar.scroller.y < top) {
						myBar.scroller.y = top;
					}
					startScroll();
				}
			}
		}
		
		public function downArrowHandler(event:Event):void 
		{
			if (isDown) {
				if (myBar.scroller.y < dragBot) {
					myBar.scroller.y += arrowMove;
					if (myBar.scroller.y > dragBot) {
						myBar.scroller.y = dragBot;
					}
					startScroll();
				}
			}
		}
		
		public function dragScroll(event:MouseEvent):void 
		{			
			myBar.scroller.startDrag(false, sRect);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, moveScroll);
		}
		
		public function mouseWheelHandler(event:MouseEvent):void 
		{
			if (event.delta < 0) {
				if (myBar.scroller.y < dragBot) {
					myBar.scroller.y-=(event.delta*2);
					if (myBar.scroller.y > dragBot) {
						myBar.scroller.y = dragBot;
					}
					startScroll();
				}
			} else {
				if (myBar.scroller.y > top) {
					myBar.scroller.y -= (event.delta*2);
					if (myBar.scroller.y < top) {
						myBar.scroller.y = top;
					}
					startScroll();
				}
			}
		}
		
		public function stopScroll(event:MouseEvent):void 
		{
			isUp = false;
			isDown = false;
			myBar.scroller.stopDrag();

			stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveScroll);
		}
		
		public function moveScroll(event:MouseEvent):void 
		{
			startScroll();
		}
		
		public function startScroll():void 
		{
			ratio = (target.height - range)/range;
			sPos = (myBar.scroller.y * ratio)-ctrl;
			
			TweenLite.to(target, timing, {y:-sPos, ease:trans}); 
		}
	}
}

An example “user” class for the Flash IDE that creates an instance of the scrollbar:

package {
		
	import com.flashscaper.BasicScrollbar;
	
	import flash.display.MovieClip;
	import flash.events.MouseEvent;

	public class Main extends MovieClip {
		
		private var scrollBr:MovieClip;
		private var whatToSroll:MovieClip;
				
		public function Main() 
		{ 		
			whatToSroll = new TextMC();	//Library asset (MovieClip with anything you like inside)			
			addChild(whatToSroll);
			
			scrollBr = new BasicScrollbar(whatToSroll, "Sine.easeOut", 2, true, 2); //uses a Library asset: Scrollbar_Lib_Asset			
			stage.addEventListener(MouseEvent.MOUSE_UP, scrollBr.stopScroll);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, scrollBr.mouseWheelHandler);			
			addChild(scrollBr);			
		}				
	}
}

Optimized XML-based AS3 Carousel

Here’s a quick way to optimize this AS3 carousel-style image gallery / menu.

After this change to Main.as there was no need to manually update the number of images in the carousel’s main class file. Just change drop the new images into the images folder & add their names as XML elements & you’re done.

  1. On line 29, change “private var numOfItems:Number = 3;” to “private var numOfItems:Number;”
  2. After line 58, add “numOfItems = imglist.length();” and let the code calculate how many elements are in your xml file
//...some code here

private var numOfItems:Number; //line 29
//...some code here

private function loadBottles(e:Event):void
{
       // pull in the xml file and load the song data
        xml = new XML(e.target.data);
			
        var imglist:XMLList = xml.bottleimages.@image; //line 58
			
	numOfItems = imglist.length();

       //some code here...

}

//some code here...

Since imglist is of type XMLList it has a built-in length() method which acts like a similar Array method and returns the number of items in the collection.