Testing Your MAMP HTML5 Content on Android Emulators

Seeing your MAMP localhost site inside a web browser in an Android Emulator

On OS X your local IP is usually http://127.0.0.1:8080, which is what’s pulled up by http://localhost:8080.
Inside an Android Emulator instance or AVD, that same IP and localhost don’t work.
Android docs point us to this number instead:

http://10.0.2.2:8080 

Seeing MAMP Virtual Hosts from inside the Android Emulator

According to this post, you need to login into the Emulator’s shell:

$adb -e shell

Then edit the Emulator’s /etc/hosts/ file to point to your http://mySite.localhost

# echo '10.0.2.2      prototypes.localhost' >> /etc/hosts

First time I tried this, I got this error:

root@android:/ # echo '10.0.2.2 prototypes.localhost' >> /etc/hosts
/system/bin/sh: can't create /etc/hosts: Read-only file system

Then I found a great step by step tutorial on the subject from http://dillieodigital.wordpress.com. His post reminded me to check that my emulator exists, has been started up and is running properly via “./android list avd”, “./adb devices”, “./adb kill server”, “./adb start server” and “./adb -s emulator-5554 remount”.

I tried his “Option 1”, pulling the Emulator’s /etc/hosts file, editing it off my Desktop and pushing it back to the Emulator but it didn’t work:

# ./adb -s emulator-5554 pull /etc/hosts /Users/your.name/Desktop
# ### edit hosts file ### 
# ./adb -s emulator-5554 push /Users/your.name/Desktop/hosts /etc/

“Option 2” from the above quoted tutorial worked fine for me, like so:

# ./adb -s emulator-5554 shell
# echo '10.0.2.2   prototypes.localhost' >> /etc/hosts
# exit

Now, if I ever get that “can’t create /etc/hosts: Read-only file system” error from the Emulator’s shell, I have to “exit” of the Emulator’s shell and remount again:

"./adb -s emulator-5554 remount" 

Then, “Option 2” works fine for me again.

Encouraging Stats for Unity3D as a Mobile Games Dev Platform but There’s Still a Reality Check for Non-Indie Developers

Interesting statistics from Game Developer magazine (May 2012), via Gamasutra:

…Game Developer magazine has released the results of its first-ever mobile and social developer technology survey in the May 2012 issue….

Unity topped the list of mobile game engines, with 53.1% of developers reporting using Unity compared to 39.8% using a custom engine., 17.7% using Cocos2D, 5.3% using Marmalade, and 5.3% using Corona (this question wasn’t exclusive, so the percentage count adds up to more than 100%).

I’m guessing most of the 53% are indie developers for now.

At the moment (Fri, Aug 24, 2012), I did a search for “Unity3D” on Indeed.com in all of the US, and got less than 100 job postings, many of them only mentioned Unity3D as a “nice to have”. Compared that with 8,828 jobs that mention HTML5, 12,567 for Flash (yes, still!), 2,087 for Actionscript (even after “Flash found dead in seedy San Jose motel room“) on the same date in all of the US.

I hope more Tech Directors & teams hear about Unity soon & pick it as their dev tool for mobile games & beyond. I like working in this game engine, so it’d be nice to have more job opportunities for it in more places.

HTML5 Slideshow Using TweenLite & jQuery

Here’s a working Demo.

Animation with TweenLite & Reducing Code Clutter with jQuery

If you’ve never developed web or mobile applications in Actionscript, Flash or Flex, you may have no idea of how awesome TweenLite, TweenMax, TimelineLite, etc are for code-based animation. Now it’s available for HTML5 & Javascript/CSS3 animation. It’s possible to use TweenLite with Canvas & WebGL. TweenLite uses the requestAnimationFrame API, which means javascript animation speed is pretty fast — in other words, it’s device battery & performance friendly. The above demo should work at roughly the same speed between your smart phone & a desktop browser.

UPDATE 06.14.2012: A friend reported some choppiness in the animation on his Galaxy S2, using the default Ice Cream Sandwich browser. My guess is the Power4.easeOut easing equation is the culprit. I’ve had to try a few easing settings to get rid of choppiness in some desktop browsers for this example. It could also be the way this example is built with all the slides pre-positioned horizontally the entire time (instead of being swapped into place as needed, when needed). Overall, he said it’s working OK. The animation runs smooth on an iPhone 4S (A5 processor at 800-1ghz, roughly the same as Galaxy S2’s processor).

In this example, I used jQuery to reduce the amount of code I had to write to dynamically assign an onclick listener to every button. It also helped me reduce the amount of cross-browser code needed to use event listeners in Javascript for the Web. Working with Javascript in Unity3D is so much more pleasant, since there are no DOM differences to worry about in that environment.

Here’s the main animation code, marquee.js:

var SLIDEITBNR = SLIDEITBNR || {}; 

(function($){

	SLIDEITBNR.slideshow = function(){

		var sl = document.getElementById('inrSlides');
		var xpos = [];
		var slideWidth = 620;
		var totalSlides = 5;

		function moveSlide(p)
		{   
		   TweenLite.to(sl, 1, { css:{ left:xpos[p] }, ease:Power4.easeOut }); //overwrite:false ????
		}	

		return { 
			
			init: function()
			{			          
			     //xpos = [0, -620, -1240, -1860, -2480];   
			     //xpos = [ -(slideWidth*0), -(slideWidth*1), -(slideWidth*2), -(slideWidth*3), -(slideWidth*4) ]; 
			     for( var i=0; i < totalSlides; i++) 
			     {
			     	xpos[i] = -(slideWidth * i);
			     }			     	

			 	$(".slidebtns").each( function(index, element) {
					$( element ).click( function(){						
						return moveSlide(index);
					});
				});	 
			}
		}
			
	}();	

	SLIDEITBNR.slideshow.init();

})($);	

Web Fonts with @font-face

I grabbed two free-for-commercial-use fonts from Font Squirrel, Oxygen and Distant Galaxy. I used the CodeAndMore.com free web font converter to generate the various formats like .woff, .svg, etc.

Here’s fonts.css:

@font-face {
	font-family: "Distant Galaxy";
	src: url(fonts/eot/distgrg_.eot);
	src: local('Distant Galaxy'),
	     url(fonts/woff/distgrg_.woff) format("woff"), 
		 url(fonts/ttf/distgrg_.ttf) format("truetype"),
		 url(fonts/svg/distgrg_.svg) format("svg");
	font-style: normal;
	font-weight: bold;
}

@font-face {
	font-family: "Oxygen";
	src: url(fonts/eot/oxygen.eot);
	src: local('Oxygen'), 
	     url(fonts/woff/oxygen.woff) format("woff"),
	     url(fonts/ttf/oxygen.ttf) format("truetype"),
	     url(fonts/svg/oxygen.svg) format("svg");
	font-style: normal;
	font-weight: normal;
}

I use the Web Fonts by referring to them by name in like so (see the next css source example below)

font-family: "Distant Galaxy",Arial,Helvetica,sans-serif;    

CSS-based layout with horizontal unordered lists & divs

* { 
	margin:0; 
	padding:0; 
}

body {
    position:absolute;
    top: 0px;
    left: 0px;
    margin: 0 auto;
    background: #606956;
}

ul {
	list-style-type: none;	
	/*width: 3100px; 620 x 5 slides */
	padding:0;
	margin: 0 auto;	 
}

li {
	float:left;
	padding: 0px;	
	/* -webkit-box-shadow:0px 0px 10px rgba(0,0,0,0.5); */
    /* border-radius: 25px;*/ 
    /*-moz-border-radius: 25px;	*/
}

#allSlides {
  position:absolute;
  top: 9px;
  left: 11px;	
  width: 620px;
  height: 250px;
  overflow: hidden;
  border-radius: 15px;  
  background: #DDD;
  border-radius: 25px;
}

#inrSlides {
  position:absolute;
  top: 0px;
  left: 0px;	
  list-style-type: none;	
  width: 3100px; /* 620 x 5 slides */
	padding:0;
  overflow: hidden;
	margin: 0 auto;		
}

#allSlideBtns {
    position:absolute;
    top: 214px;
    left: 42px;	
    width: 590px;
    height: 50px;
    font-family: "Distant Galaxy",Arial,Helvetica,sans-serif;    
    line-height: 110%;
    color: #FFF;   
    font-weight: bold;  
} 

#allSlideBtns a {
    color: #650360;   
}

#allSlideBtns li {
	padding-right: 10px;	
}

.slide1 {
  position:relative;  
  top: 0px;
  left: 0px;    
  width: 620px;
  height: 250px;
  background-image: url("../images/slide.jpg");       
}

.hdr {
  position:relative;  
  top: 20px;
  left: 30px;      
  font-family: "Distant Galaxy",Arial,Helvetica,sans-serif;
  line-height: 100%;
  letter-spacing:0.05em;
  color: #650360;
  margin: 0;
  width: 800px;
  font-weight: bold;  
  font-size: 30pt;
}

.copy {
  position:relative;  
  top: 40px;
  left: 30px;
  font-family: "Oxygen",Arial,Helvetica,sans-serif;
  line-height: 110%; 
  color: #650360;  
  width: 570px;     
  font-size: 14pt;    
}

.slidebtns {
    
}

The HTML

Note, that to animate CSS properties like “top” and “left” with TweenLite you need to activate the CSSPlugin. Sorry, used an older version of jQuery ’cause i was in a rush & lazy. It happens.

<!DOCTYPE html>
<html> 
    <head>
        <meta charset="utf-8" />    
        <link rel="stylesheet" type="text/css" href="css/fonts.css" />        
        <link rel="stylesheet" type="text/css" href="css/marquee.css" /> 

        <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
        <!-- the above version of jQuery causes this Warning in Chrome: 

             event.layerX and event.layerY are broken and deprecated in WebKit. They will be removed from the engine in the near future.
        -->                
        <title>slide show</title> 
    </head>
    <body> 

        <div id="allSlides">
          <ul id="inrSlides">          
            <li>
                <div class="slide1">
                    <div class="hdr">HEADLINE, 1ST SLIDE</div>
                    <div class="copy">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quis urna at lectus tincidunt malesuada nec non dolor. Suspendisse a diam quam, sed volutpat erat. Mauris tempus diam eu lacus fringilla adipiscing. Fusce at lorem mauris, vel ultricies tellus. Praesent dapibus mauris congue est sollicitudin...</div>
                </div>
            </li>
            <li>
                <div class="slide1">
                    <div class="hdr">HEADLINE, 2ND SLIDE</div>
                    <div class="copy">Suspendisse erat augue, pharetra sit amet lacinia vestibulum, auctor id risus. Nulla facilisi. Nulla a nunc nec ipsum varius congue vel at magna. Sed vitae congue nisl. Aliquam vel imperdiet risus. Suspendisse sit amet sapien ut neque mattis imperdiet quis et nibh. Cras placerat nunc non purus vulputate condimentum...</div>
                </div>
            </li>
            <li>
                <div class="slide1">
                    <div class="hdr">HEADLINE, 3RD SLIDE</div>
                    <div class="copy">Proin ultrices risus sed massa adipiscing congue placerat sapien venenatis. Vestibulum eget dapibus lacus. Vestibulum justo metus, pharetra ac elementum ac, sagittis nec quam. Quisque ac augue felis. Pellentesque condimentum vulputate ornare.</div>
                </div>
            </li>
            <li>
                <div class="slide1">
                    <div class="hdr">HEADLINE, 4TH SLIDE</div>
                    <div class="copy">Curabitur elementum quam posuere risus ultrices quis congue massa scelerisque. Praesent mattis sodales sodales. Fusce volutpat purus nec justo pharetra ut consequat leo fringilla. Mauris rhoncus risus at velit accumsan ut aliquet augue commodo. </div>
                </div>
            </li>           
            <li>
                <div class="slide1">
                    <div class="hdr">HEADLINE, 5TH SLIDE</div>
                    <div class="copy">Proin nibh tortor, viverra id adipiscing dignissim, cursus vel sem. Maecenas eget turpis a risus rhoncus laoreet. Donec ornare dui non nunc commodo lobortis. Curabitur posuere libero quis arcu commodo vel aliquam orci tempus. Curabitur vitae orci mauris, at pharetra enim. </div>
                </div>
            </li>               
          </ul>
        </div>        
        
        <div id="allSlideBtns">
          <ul>
            <li class="slidebtns"><a href="#">1-SLIDE</a><li>
            <li> :: <li>            
            <li class="slidebtns"><a href="#">2-SLIDE</a><li>
            <li> :: <li>                        
            <li class="slidebtns"><a href="#">3-SLIDE</a><li>
            <li> :: <li>                        
            <li class="slidebtns"><a href="#">4-SLIDE</a><li>
            <li> :: <li>           
            <li class="slidebtns"><a href="#">5-SLIDE</a><li>      
          </ul>
        </div>

        <script type="text/javascript" src="js/greensock/plugins/CSSPlugin.min.js"></script>
        <script type="text/javascript" src="js/greensock/easing/EasePack.min.js"></script>
        <script type="text/javascript" src="js/greensock/TweenLite.min.js"></script>
        <script type="text/javascript" src="js/marquee.js"></script>   

    </body>
</html>

HTML5 Offline Web Apps: Cache Manifest Gotchas Are Fun

Spent the last few days working on an iOS app, focusing on the iPhone. The goal was to make a simple HTML5 game that can be played offline, in Airplane Mode, etc.

Everything was working great. I got it on my phone & everything. Tested it on a bunch of other phones via willing victims (aka, family & friends). Then suddenly, the offline caching functionality broke. The app would load fine the first time, while online but then would fail to load again online or off.

On initial load Chrome’s console showed this:

Creating Application Cache with manifest http://myurlpath.com/my/app/path/sm2/simplegame2.appcache
Application Cache Checking event
Application Cache Downloading event
Application Cache Progress event (0 of 38) http://myurlpath.com/my/app/path/sm/images/1.png
Application Cache Progress event (1 of 38) http://myurlpath.com/my/app/path/sm/images/10.png
Application Cache Progress event (2 of 38) http://myurlpath.com/my/app/path/sm/images/card.png
Application Cache Progress event (3 of 38) http://myurlpath.com/my/app/path/sm/images/startstop3.gif
Application Cache Progress event (4 of 38) http://myurlpath.com/my/app/path/sm/images/plusminus2.gif
Application Cache Progress event (5 of 38) http://myurlpath.com/my/app/path/sm/images/pic3.png
Application Cache Progress event (6 of 38) http://myurlpath.com/my/app/path/sm/images/plusminus4.gif
Application Cache Progress event (7 of 38) http://myurlpath.com/my/app/path/sm/images/pic8.png
Application Cache Progress event (8 of 38) http://myurlpath.com/my/app/path/sm/images/time.png
Application Cache Progress event (9 of 38) http://myurlpath.com/my/app/path/sm/game.js
Application Cache Progress event (10 of 38) http://myurlpath.com/my/app/path/sm/game.css
Application Cache Progress event (11 of 38) http://myurlpath.com/my/app/path/sm/images/s.png
Application Cache Progress event (12 of 38) http://myurlpath.com/my/app/path/sm/images/8.png
Application Cache Progress event (13 of 38) http://myurlpath.com/my/app/path/sm/images/2.png
Application Cache Progress event (14 of 38) http://myurlpath.com/my/app/path/sm/images/5.png
Application Cache Progress event (15 of 38) http://myurlpath.com/my/app/path/sm/images/simplememory_icon.png
Application Cache Progress event (16 of 38) http://myurlpath.com/my/app/path/sm/images/startstop4.gif
Application Cache Progress event (17 of 38) http://myurlpath.com/my/app/path/sm/images/plusminus3.gif
Application Cache Progress event (18 of 38) http://myurlpath.com/my/app/path/sm/images/pic4.png
Application Cache Progress event (19 of 38) http://myurlpath.com/my/app/path/sm/images/startstop1.gif
Application Cache Progress event (20 of 38) http://myurlpath.com/my/app/path/sm/images/level.png
Application Cache Progress event (21 of 38) http://myurlpath.com/my/app/path/sm/images/found.png
Application Cache Progress event (22 of 38) http://myurlpath.com/my/app/path/sm/images/pic1.png
Application Cache Progress event (23 of 38) http://myurlpath.com/my/app/path/sm/images/blank.gif
Application Cache Progress event (24 of 38) http://myurlpath.com/my/app/path/sm/images/pic6.png
Application Cache Progress event (25 of 38) http://myurlpath.com/my/app/path/sm/images/9.png
Application Cache Progress event (26 of 38) http://myurlpath.com/my/app/path/sm/images/6.png
Application Cache Progress event (27 of 38) http://myurlpath.com/my/app/path/sm/images/simplememory_startup.png
Application Cache Progress event (28 of 38) http://myurlpath.com/my/app/path/sm/images/attempts.png
Application Cache Progress event (29 of 38) http://myurlpath.com/my/app/path/sm/images/3.png
Application Cache Progress event (30 of 38) http://myurlpath.com/my/app/path/sm/images/0.png
Application Cache Progress event (31 of 38) http://myurlpath.com/my/app/path/sm/images/startstop2.gif
Application Cache Progress event (32 of 38) http://myurlpath.com/my/app/path/sm/images/pic5.png
Application Cache Progress event (33 of 38) http://myurlpath.com/my/app/path/sm/images/plusminus1.gif
Application Cache Progress event (34 of 38) http://myurlpath.com/my/app/path/sm/images/pic7.png
Application Cache Progress event (35 of 38) http://myurlpath.com/my/app/path/sm/images/pic2.png
Application Cache Progress event (36 of 38) http://myurlpath.com/my/app/path/sm/images/7.png
Application Cache Progress event (37 of 38) http://myurlpath.com/my/app/path/sm/images/4.png
Application Cache Progress event (38 of 38) 
Application Cache Cached event  

all subsequent attempts to reload the page showed this confusing message in the console:

Document was loaded from Application Cache with manifest http://myurlpath.com/my/app/path/sm2/simplegame2.appcache
Application Cache Checking event
x GET http://myurlpath.com/my/app/path/sm2/game.css 
x GET http://myurlpath.com/my/app/path/sm2/game.js 
x GET http://myurlpath.com/my/app/path/sm2/images/blank.gif 
x GET http://myurlpath.com/my/app/path/sm2/images/card.png 
x GET http://myurlpath.com/my/app/path/sm2/images/startstop1.gif 
x GET http://myurlpath.com/my/app/path/sm2/images/startstop4.gif 
x GET http://myurlpath.com/my/app/path/sm2/images/level.png 
x GET http://myurlpath.com/my/app/path/sm2/images/10.png 
x GET http://myurlpath.com/my/app/path/sm2/images/plusminus3.gif 
x GET http://myurlpath.com/my/app/path/sm2/images/4.png 
x GET http://myurlpath.com/my/app/path/sm2/images/plusminus1.gif 
x GET http://myurlpath.com/my/app/path/sm2/images/time.png 
x GET http://myurlpath.com/my/app/path/sm2/images/s.png 
x GET http://myurlpath.com/my/app/path/sm2/images/attempts.png 
x GET http://myurlpath.com/my/app/path/sm2/images/found.png 
Application Cache NoUpdate event

It was a simple error. I changed my server’s directory but totally forgot to update the paths in my CACHE MANIFEST file to reflect this (duh!), so the app would load once and then fail every time after the Google Chrome’s console showing a failed “GET” request on a bunch of files at the top of my manifest. Specifically, I forgot to change this path: “http://myurlpath.com/my/app/path/sm/game.css&#8221; to this “http://myurlpath.com/my/app/path/sm2/game.css&#8221;. Manifest-Validator.com did not show me any error (naturally, it wouldn’t be able to detect human stupidity). Manifest files don’t provide anything like compiler warnings, so little things like this, even something like a hidden character for a line break can screw things up easily. A similar error, only involving case sensitivity of manifest files is described in the solution here.

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

Updating previous version to remove the use of “new function()” from the JS code.

Alternate JS version, without classical inheritance

The original code works fine but it feels weird to be using classical inheritance in a prototypal language – “new function” inside “TIMSHAYA.Triganimation = new function()”. Below is version of the JS code that removes “new function” and replaces it with “function()”, adding a “return” statement that in turn exposes the public “init” method, while maintaining closure (inner function init()’s access to it’s external function’s private members like “numsqr”, “drawRect()” & “animate()”).

KEEP IN MIND: using the “new function” version might turn out to be faster in some cases as far as performance goes. In this case, jsPerf shows that Chrome 16 is the only browser where “new function” is faster than the inline version (unless I’m screwing up the test somehow).

Note: Don’t forget to define requestAnimFrame (see HTML file here).

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

(function(whichCanvas){ 

   TIMSHAYA.Triganimation = 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);
                    })
                }
            }
         
            return {
                init: function() {           
                        
                         for(var i = 0; i < numsqr; i++)
                         {                     
                             mShapes[i] = {
                                angle:0,
                                width:20,
                                height:20,
                                x: 100 + i*10,
                                y: 270,
                                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");

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");

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

Just playing around with animating HTML5 Canvas elements with Javascript. I started with this basic horizontally moving rectangle, then added some trig code to move multiple objects around in a circle. Here’s a demo.

If the Math.sin() & Math.cos() is confusing, watch the video tutorials on Trig and Unit Circle in particular at the Khan Academy.

<!DOCTYPE HTML>
<html>
    <head>
        <style>
            body {
                margin: 0px;
                padding: 0px;
            }
            
            #myCanvas {
                border: 1px solid #9C9898;
            }
        </style>
        <script>
            var numsqr = 40;
            var mShapes = [];

            window.requestAnimFrame = (function(callback){
                return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(callback){
                    window.setTimeout(callback, 1000 / 60);
                };
            })();
            
            function drawRect(myRectangle){
                var canvas = document.getElementById("myCanvas");
                var context = canvas.getContext("2d");
                context.beginPath();
                context.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height);
                
                context.fillStyle = "#8ED6FF";
                context.fill();
                context.lineWidth = myRectangle.borderWidth;
                context.strokeStyle = "black";
                context.stroke();
            }
            
            function animate(lastTime,  mShapes, animProp){
                if (animProp.animate) {
                    var canvas = document.getElementById("myCanvas");
                    var context = canvas.getContext("2d");
                    
                    // update
                    var date = new Date();
                    var time = date.getTime();
                    var timeDiff = time - lastTime;
        
                    for(var j = 0; j < numsqr; j++){
                        
                        //circles around a midpoint in a larger circle, specified by the radius                              
                        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
                    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);
                    });
                }
            }
            
            window.onload = function(){

                 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
                    };
                 }
                    
                /*
                 * make the animation properties an object
                 * so that it can be modified by reference
                 * from an event
                 */
                var animProp = {
                    animate: false
                };
                
                // add click listener to canvas
                document.getElementById("myCanvas").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] );   
                 
            };
        </script>
    </head>
    <body onmousedown="return false;">
        <canvas id="myCanvas" width="600" height="600"></canvas>
    </body>
</html>