Categories
Articles

PhoneGap Cordova Accelerometer HTML5 Canvas Animation XCode Example


This is a template example using the accelerometer with Cordova or Phonegap and animating a sprite on the HTML5 canvas.blog_image400x545

Learn cocos2D Game Development
PhoneGap Essentials: Building Cross-Platform Mobile Apps

I was working on a game using the html canvas and the accelerometer. I developed a way to combine both in IOS using Objective C and the UIWebView in the UIKit. That approach limits distribution to the IOS platform but allows you to have all of the native UI items like screen navigation and just use the canvas for animations.

Then I became curious about distributing on multiple devices containing an accelerometer since the animation is being done in html canvas. The solution was a hybrid mobile platform like Cordova. UI in Cordova is really up to you although you can find UI libraries written in HTML, JavaScript and JQuery optimized for mobile.

Here are screen shots for the app.

There is only one screen and these are just showing the animated red circle in different positions. The functionality is simple in that the red circle moves in the direction of the accelerometer’s x and y values – well with some adjustments but we will cover them in a bit.

When it reaches the boundaries of the canvas for either direction, it stop moving in that one direction. Thus you can move it to any corner and it will stay fixed and you can move it around the edges of the canvas.

You can also tap the canvas to start and stop the animation.

The Project Files

The project uses Cordova 1.6, XCode 4.3.2 and was tested using iOS 5.1 on a IPhone 4.

I had started the example using Cordova 1.5. Cordova 1.6 was released before completing this article and Cordova 1.6 changed the accelerometer values. The release notes included “Rewrite of accelerometer code and removed DeviceInfo old init approach” and “Added unification of accelerometer values on ios (based on android values)”. Boiling those entries down we get that the values are the same across platform, but if you are using IOS you better divide by 10. I commented in the code in case you are still using 1.5 or earlier.

I added a simple html console area to display debugging messages from the Javascript.

I also stripped out most of the extra Cordova comments that were not relevant to this example.

Download XCode Project Files

Step 1 – Install Cordova for XCode

Instructions for setting up your development environment are located at the PhoneGap site Getting Started with iOS.

Step 2 – Create New Cordova-based Application

Step 3 – Set XCode Project Options


Set the XCode project options as follows:

  • Product name: Here I used CordovaAccelerometerCanvas as the project name. You can use a name of your own choosing.
  • Company identifier: Provide your own reverse domain name.
  • Use Automatic Reference Counting: Uncheck

Step 4 – Choose A File Location

Once you select a file location on your computer you will have a folder structure as follows:

And in XCode you will see a project window as follows:

You may notice the project has a warning. Ignore this for now.

Step 5 – Create and Add the www Folder and Files to the Project

These are the normal setup instructions for a Cordova alias PhoneGap XCode project. You can skip this step if you are used to creating Cordova XCode projects.

The process is creating a www folder by running the app once in the IPhone simulator and then add to the project explorer.

First run the app in the IPhone simulator.

The app runs but complains of the missing index.html file.

A www folder with this file and one other js file were created on your file system as this the app launched in the simulator. Here you see them and you need to drag the www folder into the Project Explorer. Do not drag to or copy to the XCode folders outside of XCode.

Fill out the “Choose options for adding these files” dialog as follows.

  • Destination: Unchecked
  • Folders: “Created folder references for added folders” selected.
  • Add to targets: CordovaAccelerometerCanvas checked.

And here are the final results you see in the Project Explorer window.

Run the app in the IPhone simulator one more time.

This time you will be greeted with an Alert dialog with the “Cordova is working” message.

Step 7a – The Completed index.html File

Here is the full index.html file completed for your copy convenience. Just replace your index.html file with this and you can test on your device.

I removed Cordova code comments and commented code not related to our needs that is included in the index.html file.

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
        <meta charset="utf-8">
        <script type="text/javascript" charset="utf-8" src="cordova-1.6.0.js"></script>
        <script type="text/javascript">
            var console_log;                // Debugging div on device
            var canvas_ctx;                 // HTML canvas 2d context
            var SPEED = 10;                 // Canvas redraw speed in milliseconds and accelerometer frequency
            var DISTANCE_FACTOR = .1;       // Factor to adjust accelerometer values to screen distance. Cordova 1.6 changed to values to be Android compatible which appeared to be * 10. For Cordova 1.5 make this value 1.
            var ax = 0;                     // Acceleration x axis (Accelerometer value adjusted for direction)
            var ay = 0;                     // Acceleration y axis (Accelerometer value adjusted for direction)
            var x;                          // Circle x position
            var y;                          // Circle y position
            var vx = 0;                     // Velocity x axis
            var vy = 0;                     // Velocity y axis
            var WIDTH = 320;                // Width of canvas
            var HEIGHT = 300;               // Height of canvas
            var RADIUS = 50;                // Width of circle object
            var CIRCLE_COLOR = "#f00";      // Circle color
            var CANVAS_COLOR = "#FAF7F8";   // Color of canvas background
            var watchID;                    // Accelerometer.watchAcceleration return value. 
            var drawID;                     // Draw time interval. 
            var playing = true;             // Boolean if animation is playing.
            /* DOM body onload event callback */
            function onBodyLoad()
            {		
                document.addEventListener("deviceready", onDeviceReady, false);
            }
            /* Cordova has been initialized and is ready to roll */
            function onDeviceReady()
            {
                console.log('console_div');
                console_log = document.getElementById('console_div');
                console_log.innerHTML += "onDeviceReady()<br/>";
                init();
            }
            /* Initialize canvas and animation */
            function init() 
            {
                var canvas = document.getElementById("canvas");
                canvas_ctx = canvas.getContext("2d");
                // Center 
                x = WIDTH / 2 ;
                y = HEIGHT/ 2 ;
                startPlay();
            }
            /* Start watching the accelerometer */
            function startWatch() 
            {
                var options = { frequency: SPEED };
                watchID = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);
            }   
            // Stop watching the accelerometer
            function stopWatch() 
            {
                if (watchID) {
                    navigator.accelerometer.clearWatch(watchID);
                    watchID = null;
                }
            }
            /* Accelerometer data callback */
            function onSuccess( acceleration )
            {
                // Set drawing acceleration values
                ax = acceleration.x * DISTANCE_FACTOR * -1; // -1 to change direction for Cordova 1.6. Removed for Cordova 1.5.
                ay = acceleration.y * DISTANCE_FACTOR ;// Add * -1 for Cordova 1.5;
                // Optional ouput for understanding accelerator values.
                console_log.innerHTML = 
                'Acceleration X: ' + acceleration.x + '<br />' +
                'Acceleration Y: ' + acceleration.y + '<br />' +
                'Acceleration Z: ' + acceleration.z + '<br />' +
                'Timestamp: '      + acceleration.timestamp ;
            }
            /*  Accelerometer error callback */
            function onError()
            {
                alert("Accelerometer Error");
            }
            /* Steps to start animation play */
            function startPlay()
            {
                playing = true;
                vx = 0;
                vy = 0;
                startWatch();
                drawID = setInterval(draw, SPEED);
            }
            /* Steps to stop animation play */
            function stopPlay()
            {
                clearInterval(drawID);
                stopWatch();
                playing = false;
            }
            /* Draw circle */   
            function circle( x, y, r ) 
            {
                canvas_ctx.beginPath();
                canvas_ctx.arc(x, y, r, 0, Math.PI*2, true);
                canvas_ctx.fill();
            }
            /* Draw rectangle */
            function rect( x, y, w, h ) 
            {
                canvas_ctx.beginPath();
                canvas_ctx.rect(x,y,w,h);
                canvas_ctx.closePath();
                canvas_ctx.fill();
            }
            /* Clear canvas */
            function clear() 
            {
                canvas_ctx.clearRect(0, 0, WIDTH, HEIGHT);
            }
            /* Compute drawing metrics and draw frame */    
            function draw() 
            {
                // Increase velocity by acceleration
                vx += ax;
                vy += ay;
                // Update circle drawing position.
                x += vx;
                y += vy;
                /* Boundaries testing */
                // Right boundary
                if ( x + RADIUS > WIDTH  )
                {
                    x = WIDTH - RADIUS ;
                    vx = 0;
                }
                // Left boundary
                if (x - RADIUS  <= 0)
                {
                    x = RADIUS   ;
                    vx = 0;
                }
                // Bottom boundary
                if (y +  RADIUS  > HEIGHT)
                {
                    y = HEIGHT - RADIUS ;
                    vy = 0;
                }
                // Top boundary
                if (y - RADIUS  <= 0)
                {
                    y = RADIUS  ;
                    vy = 0;
                }
                
                // Debugging info.
                //console_log.innerHTML = 
                //'x: ' + x + '<br />' +
                //'y: ' + y + '<br />' +
                //'vx: ' + vx + '<br />' +
                //'vy: ' + vy + '<br />' +
                //'ax: ' + ax + '<br />' +
                //'ay: ' + ay + '<br />' ;    
                
                /* Draw frame */
                // Clear canvas
                clear();
                // Draw canvas background
                canvas_ctx.fillStyle = CANVAS_COLOR;
                rect( 0, 0, WIDTH, HEIGHT );
                /* Draw circle */
                canvas_ctx.fillStyle = CIRCLE_COLOR;
                circle( x, y, RADIUS );
            }
            /* Canvas tag touch end event handler */
            function canvasTouchEnd()
            {
                if (playing)
                {
                    stopPlay();
                }
                else
                {
                    startPlay();
                }
            }
        </script>
    </head>
    <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;margin:0px;">
        <div>
            <h1 style = "font-size:20px;margin-bottom:0px;margin-top:0px;padding-top:0px;">Accelerometer + HTML5 Canvas</h1>
            <canvas id="canvas" width="320" height="300" ontouchend = "canvasTouchEnd();" >
                This text is displayed if your browser 
                does not support HTML5 Canvas.
            </canvas>
            <div id = "console_div" style = "position:absolute;text-align:left;margin:2px;border:1px solid black;background-color:#fff;top:330px;left:0px;width:314px;height:118px;overflow:auto;" 
                >
            </div>
        </div>
    </body>
</html>

The remaining part of this article will explain the parts of the index.html file by topic.

Step 7a – The index.html body Section

Key in this section is the canvas tag on line 189. I am using the ontouchend event versus the onclick event to prevent a canvas flicker when the screen is touched. The action is to start and stop the animation.

Line 193 is my own html debugging console for simple examples. You should see the accelerometer values displayed in here when testing on a device.

 <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;margin:0px;">
        <div>
            <h1 style = "font-size:20px;margin-bottom:0px;margin-top:0px;padding-top:0px;">Accelerometer + HTML5 Canvas</h1>
            <canvas id="canvas" width="320" height="300" ontouchend = "canvasTouchEnd();" >
                This text is displayed if your browser 
                does not support HTML5 Canvas.
            </canvas>
            <div id = "console_div" style = "position:absolute;text-align:left;margin:2px;border:1px solid black;background-color:#fff;top:330px;left:0px;width:314px;height:118px;overflow:auto;" 
                >
            </div>
        </div>
    </body>

[ad name=”Google Adsense”]
Step 7b – The index.html Accelerometer Code

Cordova provides an api for the accelerometer. We use a good portion of it with this example.

The variables on lines 11 to 14 are helpers for the animation. We do not need them for raw access to the accelerometer. You can see their explanations and we will look at them applied further along.

            var SPEED = 10;                 // Canvas redraw speed in milliseconds and accelerometer frequency
            var DISTANCE_FACTOR = .1;       // Factor to adjust accelerometer values to screen distance. Cordova 1.6 changed to values to be Android compatible which appeared to be * 10. For Cordova 1.5 make this value 1.
            var ax = 0;                     // Acceleration x axis (Accelerometer value adjusted for direction)
            var ay = 0;                     // Acceleration y axis (Accelerometer value adjusted for direction)

Line 24 has an id for the accelerometer watch event. This can be used to clear the watch event when you do not need the accelerometer anymore.

            var watchID;                    // Accelerometer.watchAcceleration return value. 

I created the startWatch function for this example to call when we need to start collecting accelerometer data.

The accelerometer works by calling your own event handler functions using the watchAcceleration method you see on line 54.
The watchAcceleration take a reference to your success and error handler as the first two arguments.

It also takes an options object for the third argument. Currently the only option object key is frequency measured in milliseconds. I am using the SPEED variable set to 10 milliseconds for the frequency.

The watchAcceleration returns an id so we can refer to it later.

            /* Start watching the accelerometer */
            function startWatch() 
            {
                var options = { frequency: SPEED };
                watchID = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);
            }   

The stopWatch function is also created for the example to stop requesting accelerometer data anywhere in the code. Line 60 uses the clearWatch method to stop watching the accelerometer. The watchID variable from line 54 identifies the watch activity to end.

To prevent throwing errors, logic is included to test for the watchID.

            // Stop watching the accelerometer
            function stopWatch() 
            {
                if (watchID) {
                    navigator.accelerometer.clearWatch(watchID);
                    watchID = null;
                }
            }

The onSuccess function on line 64 is called from the watchAcceleration on line 54.

The onSuccess function passes an acceleration object. The acceleration object currently has four values and they are all displayed in our html console on line 71.

The other code in the onSuccess method prepares variables for the animation.

The ax and ay variables on line 68 and 69 are accelerometer x and y values respectively. They represent acceleration in our animation.

For the x-axis value we need to reverse the value sign so the tilt of the device represents the direction of the x-axis animation. Interesting this is for Cordova 1.6. In Cordova 1.5 this is not needed.

For the y-axis value we need to reverse the value sign if you are using Cordova 1.5. Again to represent the direction by the tilt of the device.

Finally the x and y acceleration values were adjusted with Cordova 1.6. As I had mentioned I started the example in Cordova 1.5 and was able to use the raw values. But with Cordova 1.6 the values are 10x so I added the DISTANCE_FACTOR variable set to .1 to compensate. Perhaps I should have called it a “VERSION_FACTOR”.

I had no need for z and timestamp properties in this example, but they are displayed for interest. You may get updates from the device due to your frequency of request, but the device has not sensed new values, so you can use the timestamp property in case you are using the accelerometer updates for a cpu intensive function that without a change is unnecessary.

            /* Accelerometer data callback */
            function onSuccess( acceleration )
            {
                // Set drawing acceleration values
                ax = acceleration.x * DISTANCE_FACTOR * -1; // -1 to change direction for Cordova 1.6. Removed for Cordova 1.5.
                ay = acceleration.y * DISTANCE_FACTOR ;// Add * -1 for Cordova 1.5;
                // Optional ouput for understanding accelerator values.
                console_log.innerHTML = 
                'Acceleration X: ' + acceleration.x + '<br />' +
                'Acceleration Y: ' + acceleration.y + '<br />' +
                'Acceleration Z: ' + acceleration.z + '<br />' +
                'Timestamp: '      + acceleration.timestamp ;
            }

This final code snippet is simply the error handler for watching the accelerometer. Apparently there is no information provided for the error, so you will need to improvise.


            /*  Accelerometer error callback */
            function onError()
            {
                alert("Accelerometer Error");
            }

Step 7c – User Start and Stop Interaction

Touching the canvas starts and stops the animation. This starts on line 189 with the canvas tag ontouchend event calling the canvasTouchEnd() function on line 172.

           <canvas id="canvas" width="320" height="300" ontouchend = "canvasTouchEnd();" >
                This text is displayed if your browser 
                does not support HTML5 Canvas.
            </canvas>

There is the playing variable set on line 26 that retains the state of animation playing.

            var playing = true;             // Boolean if animation is playing.

The canvasTouchEnd function uses the playing boolean variable to determine starting or stopping animation and calls function to handle changing the animation playing state.

            /* Canvas tag touch end event handler */
            function canvasTouchEnd()
            {
                if (playing)
                {
                    stopPlay();
                }
                else
                {
                    startPlay();
                }
            }

The startPlay function first sets the playing state to true.

To start playing the velocity variables are set to zero. This prevents any timing issues that may cause the animation move in a direction not indicative of the tilt of the device when the animation is restarted.

Receiving events from the accelerometer is started with the call to the startWatch function we reviewed earlier.

Handing the canvas redraw is done on line 89 with a JavaScript timer. The timing of the of the canvas redraw is the same as the updates from the accelerometer. You could consider a different design where the accelerometer updates perform the canvas redraw. However the approach we are using allows animations to occur that not dependent on the accelerometer changes should you need to add them.

            /* Steps to start animation play */
            function startPlay()
            {
                playing = true;
                vx = 0;
                vy = 0;
                startWatch();
                drawID = setInterval(draw, SPEED);
            }

Stopping animation is done by clearing the timer interval, stopping the accelerometer watching and setting the playing state to false.

            /* Steps to stop animation play */
            function stopPlay()
            {
                clearInterval(drawID);
                stopWatch();
                playing = false;
            }

Step 7d – The index.html Canvas Animation

The canvas animation does not require Cordova. It is just an interesting way to demonstrate using the accelerometer. All the animation in the canvas could be done in any HTML5 web browser.

First is the canvas tag which has a predefined width and height. We could create the canvas tag dynamically in JavaScript, but I left it out to keep code simpler.

           <canvas id="canvas" width="320" height="300" ontouchend = "canvasTouchEnd();" >
                This text is displayed if your browser 
                does not support HTML5 Canvas.
            </canvas>

Next there are some variables that impact the canvas animation work.

Line 10 has a variable to reference the canvas 2d context to allow drawing. This will refer back to the canvas tag.

The SPEED variable on line 11 is for the JavaScript timer that will call the draw function. The draw function does all the work on the canvas.

Line 13 and 14 are acceleration values that are updated using the accelerometer.

The x and y on lines 14 and 15 are the position of the circle sprite we are animating.

The velocity variables are initialized to zero on lines 16 and 17.

Height and width of the canvas are repeated here for computing the boundaries for our animated circle sprite.

The diameter of the circle sprite is configurable on line 21

Adding some color on lines 22 and 23 for the circle sprite and background respectively.

Then we have on line 25 the JavaScript timer interval id for redrawing the canvas.

            var console_log;                // Debugging div on device
            var canvas_ctx;                 // HTML canvas 2d context
            var SPEED = 10;                 // Canvas redraw speed in milliseconds and accelerometer frequency
            var DISTANCE_FACTOR = .1;       // Factor to adjust accelerometer values to screen distance. Cordova 1.6 changed to values to be Android compatible which appeared to be * 10. For Cordova 1.5 make this value 1.
            var ax = 0;                     // Acceleration x axis (Accelerometer value adjusted for direction)
            var ay = 0;                     // Acceleration y axis (Accelerometer value adjusted for direction)
            var x;                          // Circle x position
            var y;                          // Circle y position
            var vx = 0;                     // Velocity x axis
            var vy = 0;                     // Velocity y axis
            var WIDTH = 320;                // Width of canvas
            var HEIGHT = 300;               // Height of canvas
            var RADIUS = 50;                // Width of circle object
            var CIRCLE_COLOR = "#f00";      // Circle color
            var CANVAS_COLOR = "#FAF7F8";   // Color of canvas background
            var watchID;                    // Accelerometer.watchAcceleration return value. 
            var drawID;                     // Draw time interval. 
            var playing = true;             // Boolean if animation is playing.

The init() function is called when the app boots up and here you see on line 43 and 44 the standard way to get the drawing context to the canvas tag.

Also the starting position of the circle sprite is placed at the center of the screen.

            /* Initialize canvas and animation */
            function init() 
            {
                var canvas = document.getElementById("canvas");
                canvas_ctx = canvas.getContext("2d");
                // Center 
                x = WIDTH / 2 ;
                y = HEIGHT/ 2 ;
                startPlay();
            }

There are some utility functions for drawing shapes in the html canvas. First we have the circle method which demonstrates how to draw a circle given x, y and radius values.

Discussing the values for drawing on the canvas are beyond the scope of this article but I strongly suggest you get a copy of Foundation HTML5 Animation with JavaScript.

The values for the canvas 2d context arc method are centerX, centerY, radius, startingAngle in radians, endingAngle in radians and the boolean antiClockwise.

The startingAngle and endingAngle have radian values to draw a complete circle.

Note you will need to compensate for x and y because the values for x and y reference the center. You will see this offset in the boundary testing code.

            /* Draw circle */   
            function circle( x, y, r ) 
            {
                canvas_ctx.beginPath();
                canvas_ctx.arc(x, y, r, 0, Math.PI*2, true);
                canvas_ctx.fill();
            }

Next is the rect function on line 106 to draw a rectangle. The canvas 2d context rect method is straightforward drawing from top left corner of the rectangle.

            /* Draw rectangle */
            function rect( x, y, w, h ) 
            {
                canvas_ctx.beginPath();
                canvas_ctx.rect(x,y,w,h);
                canvas_ctx.closePath();
                canvas_ctx.fill();
            }

In drawing for animation, you will need to clear the canvas, so this clear function allows that to happen. The one line is clearRect method of the canvas 2d context. The clearRect method clears a rectangular area and here we have the entire canvas covered.


            /* Clear canvas */
            function clear() 
            {
                canvas_ctx.clearRect(0, 0, WIDTH, HEIGHT);
            }

The drawing position computations and actual drawing are both done in the draw function. Some animation applications you may want to split the drawing from the computations, but in this case putting them together meets our needs.

The velocity in the x and y directions are computed on lines 122 and 123. Simply they are increased or decreased by the change in acceleration values from the accelerometer. Acceleration values contain direction as well as acceleration.

Then we recompute the circle sprite position on lines 125 and 126.

Lines 127 – 151 are computing the boundaries for the canvas in relation to the proposed position of the center point, the x and y values, of the circle sprite. If any boundary is reached, then the velocity for that direction is set to zero and the position value is computed to keep the circle sprite in view at the edge.

I left the debugging information lines in but commented. If you use them, then comment lines 71 – 75.

Final work is to draw and this starts on line 164 where the canvas is cleared of all drawings.

Then lines 166 and 167 get our canvas background drawn.

The circle sprite is drawn on lines 169 and 170.

            /* Compute drawing metrics and draw frame */    
            function draw() 
            {
                // Increase velocity by acceleration
                vx += ax;
                vy += ay;
                // Update circle drawing position.
                x += vx;
                y += vy;
                /* Boundaries testing */
                // Right boundary
                if ( x + RADIUS > WIDTH  )
                {
                    x = WIDTH - RADIUS ;
                    vx = 0;
                }
                // Left boundary
                if (x - RADIUS  <= 0)
                {
                    x = RADIUS   ;
                    vx = 0;
                }
                // Bottom boundary
                if (y +  RADIUS  > HEIGHT)
                {
                    y = HEIGHT - RADIUS ;
                    vy = 0;
                }
                // Top boundary
                if (y - RADIUS  <= 0)
                {
                    y = RADIUS  ;
                    vy = 0;
                }
                
                // Debugging info.
                //console_log.innerHTML = 
                //'x: ' + x + '<br />' +
                //'y: ' + y + '<br />' +
                //'vx: ' + vx + '<br />' +
                //'vy: ' + vy + '<br />' +
                //'ax: ' + ax + '<br />' +
                //'ay: ' + ay + '<br />' ;    
                
                /* Draw frame */
                // Clear canvas
                clear();
                // Draw canvas background
                canvas_ctx.fillStyle = CANVAS_COLOR;
                rect( 0, 0, WIDTH, HEIGHT );
                /* Draw circle */
                canvas_ctx.fillStyle = CIRCLE_COLOR;
                circle( x, y, RADIUS );
            }

Good luck!

[ad name=”Google Adsense”]

Categories
Articles

PhoneGap Cordova Camera XCode Example


This is a bare bones example of using Cordova or Phonegap to take a photo with your IPhone or IPad camera.

Learn cocos2D Game Development
PhoneGap Essentials: Building Cross-Platform Mobile Apps

The project uses XCode 4.3.2 and was tested using iOS 5.1 on a IPhone 4. There are no frills. I did make the button large enough for IPhone human guideline standards. I also added a simple console area to display debugging messages from the Javascript.

I also stripped out most of the extra Cordova comments that were not relevant.

Each time you take a picture another file is added. This example does not provide a way to delete the files. If you remove the app from the phone, the images are deleted. You may need to study the Cordova File API for how to manage your app’s local storage.

Here are the screens for the app.

The left screen is the app before any photo has been taken. It shows the integrated debugging console with the logged message that the Cordova onDeviceReady() method was called.

The middle screen is the IPhone Camera app with a photo just taken.

The right screen shows the app with a photo taken. You also see in the integrated console messages logged from the Javascript. The second line is the method called by touching the “Take Picture” button. The final message is the callback method from the Cordova getPicture camera method for a successful photo take. In particular you can see the file path and name of the photo.

Download XCode Project Files

Step 1 – Install Cordova for XCode

Instructions for setting up your development environment are located at the PhoneGap site Getting Started with iOS.

Step 2 – Create New Cordova-based Application

Step 3 – Set XCode Project Options


Set the XCode project options as follows:

  • Product name: Here I used CordovaCamera as the project name. You can use a name of your own choosing.
  • Company identifier: Provide your own reverse domain.
  • Use Automatic Reference Counting: Uncheck

Step 4 – Choose A File Location

Once you select a file location on your computer you will have a folder structure as follows:

And in XCode you will see a project window as follows:

You may notice the project has a warning. Ignore this for now.

Step 5 – Create and Add the www Folder and Files to the Project

These are the normal setup instructions for a Cordova alias PhoneGap XCode project. You can skip this step if you are used to creating Cordova XCode projects.

The process is creating a www folder by running the app once in the IPhone simulator and then add to the project explorer.

First run the app in the IPhone simulator.

The app runs but complains of the missing index.html file.

A www folder with this file and one other js file were created on your file system as this the app launched in the simulator. Here you see them and you need to drag the www folder into the Project Explorer. Do not drag to or copy to the XCode folders outside of XCode.

Fill out the “Choose options for adding these files” dialog as follows.

  • Destination: Unchecked
  • Folders: “Created folder references for added folders” selected.
  • Add to targets: CordovaCamera checked.

And this is the final results you see in the Project Explorer window.

Run the app in the IPhone simulator one more time.

This time you will be greeted with an Alert dialog with the “Cordova is working” message.

[ad name=”Google Adsense”]

Step 7 – Code the index.html File

I removed code comments and commented code that is included in the index.html file.

Here is the full index.html file completed for your copy convenience.

<!DOCTYPE html>
<html>
  <head>
  <title>Camera</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
	<meta charset="utf-8">
	<script type="text/javascript" charset="utf-8" src="cordova-1.5.0.js"></script>
    <script type="text/javascript">
    // Debugging div on device
    var console_log;
	
	function onBodyLoad()
	{		
		document.addEventListener("deviceready", onDeviceReady, false);
	}
	
	/*  Cordova has been initialized and is ready to roll */
	function onDeviceReady()
	{
        console_log = document.getElementById('console_div');
        console_log.innerHTML += "onDeviceReady()<br/>";
	}
        
    /*  Open the device camera app */
    function capturePhoto() 
    {
        console_log.innerHTML += "capturePhoto()<br/>";
        // navigator.camera.getPicture( cameraSuccess, cameraError, [ cameraOptions ] );
        navigator.camera.getPicture(getPhoto, 
                                    onFail, 
                                    { 
                                    quality: 50,
                                    destinationType: Camera.DestinationType.FILE_URI, // // Return image file URI
                                    sourceType:Camera.PictureSourceType.CAMERA,
                                    targetWidth:120,  // Width in pixels to scale image. Aspect ratio is maintained. Required targetHeight.
                                    targetHeight:180  // Height in pixels to scale image. Aspect ratio is maintained. Required targetWidth.
                                    });
    }
   /* navigator.camera.getPicture success function */
    function getPhoto(imageData) 
    {
        var cameraImage = document.getElementById('cameraImage');
        cameraImage.src = imageData;
        console_log.innerHTML += "getPhoto() - cameraImage.src: " + cameraImage.src + "<br/>";
    }
    /* navigator.camera.getPicture fail function */
    function onFail(message) 
    {
        alert('Failed because: ' + message);
    }       
    </script>
  </head>
  <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;">
      <div>
          <h1 style = "margin-bottom:0px;">Cordova Camera</h1>
          <button style = "font-size:20px;width:200px;height:44px;;margin-bottom:5px;" onclick="capturePhoto();">Take Picture</button> 
          <br>
          <img style="width:120px;height:180px;;background-color:#fff;" id="cameraImage" src="" />
          <div id="console_div" style = "text-align:left;border:1px solid black;background-color:#fff;height:150px;overflow:auto;"></div>
      </div>
  </body>
</html>

First look at the html starting on line 53. To simplify, the css is placed inline with tags versus the better approach of using an external css file.

  <body onload="onBodyLoad()" style = "text-align:center;background-color:#ccc;padding:0px;">
      <div>
          <h1 style = "margin-bottom:0px;">Cordova Camera</h1>
          <button style = "font-size:20px;width:200px;height:44px;;margin-bottom:5px;" onclick="capturePhoto();">Take Picture</button> 
          <br>
          <img style="width:120px;height:180px;;background-color:#fff;" id="cameraImage" src="" />
          <div id="console_div" style = "text-align:left;border:1px solid black;background-color:#fff;height:150px;overflow:auto;"></div>
      </div>
  </body>

Line 54 is the container div.

Line 55 provides a simple page heading.

Line 56 is the “Take Picture” button. It calls the capturePhoto() method when touched. Notice that you use the onClick handler, Cordova does the translation to touch up event for you.

Line 57 is the img tag for the photo once we take a picture.

Line 58 is a debugging div for output from your javascript. There are other debugging console solutions that may better serve your needs.

Back to the top of index.html, line 10 declares the console_log variable. It will globally reference the console_div div tag in the html. You can then see on lines 20 and 21 getting the reference from the DOM for console_div div tag.

<!DOCTYPE html>
<html>
  <head>
  <title>Camera</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
	<meta charset="utf-8">
	<script type="text/javascript" charset="utf-8" src="cordova-1.5.0.js"></script>
    <script type="text/javascript">
    // Debugging div on device
    var console_log;
	
	function onBodyLoad()
	{		
		document.addEventListener("deviceready", onDeviceReady, false);
	}
	
	/*  Cordova has been initialized and is ready to roll */
	function onDeviceReady()
	{
        console_log = document.getElementById('console_div');
        console_log.innerHTML += "onDeviceReady()<br/>";
	}

The onBodyLoad function on line 12 is called from the DOM body tag onload event. It then provides a listener to the deviceready event from Cordova.

The deviceready event calls the onDeviceReady function to get the ball rolling.

This boot strapping is all standard for Cordova apps as it has since it was called PhoneGap.

Moving down to line 24 the capturePhoto function was added and is called by the “Take Picture” button in the html.

The function logs to our makeshift console on line 27.

The key item is the Cordova Camera API being used on line 29.

You see the Cordova API is attached to the DOM navigator object.

In this case we are using the camera.getPicture method.

The first argument is the function to call for a successful use of the device camera and the second is for failed usage of the device camera.

    /*  Open the device camera app */
    function capturePhoto() 
    {
        console_log.innerHTML += "capturePhoto()<br/>";
        // navigator.camera.getPicture( cameraSuccess, cameraError, [ cameraOptions ] );
        navigator.camera.getPicture(getPhoto, 
                                    onFail, 
                                    { 
                                    quality: 50,
                                    destinationType: Camera.DestinationType.FILE_URI, // // Return image file URI
                                    sourceType:Camera.PictureSourceType.CAMERA,
                                    targetWidth:120,  // Width in pixels to scale image. Aspect ratio is maintained. Required targetHeight.
                                    targetHeight:180  // Height in pixels to scale image. Aspect ratio is maintained. Required targetWidth.
                                    });
    }

The details are in the third argument which is an array of properties called cameraOptions.

For the quality option on line 32 I choose the value of 50. This is a blatant copy from the documentation. But also I read that values over 50 may become a factor in memory issues. Something for you to research if you need a quality value over 50.

On line 33 the destinationType option takes values defined by Camera.DestinationType.

For the destinationType option I choose Camera.DestinationType.FILE_URI. The Camera.DestinationType.FILE_URI sends a file url of the picture taken to the success function. The only other choice for the destinationType property as of this writing is Camera.DestinationType.DATA_URL which returns an image as base64 encoded string to the success function.

Line 34 sets the sourceType option. It uses values from Camera.PictureSourceType. The official documentation leaves the sourceType option values up to your interpretation based on naming. The sourceType option I used is Camera.PictureSourceType.CAMERA since my plan is to use the device camera. The other two values are Camera.PictureSourceType.PHOTOLIBRARY and Camera.PictureSourceType.SAVEDPHOTOALBUM.

Finally on lines 35 and 36 I included the targetWidth and targetHeight options.

I found if you omit the targetWidth and targetHeight options you may have aspect ratio issues with displaying correct scaling. I discovered this when taking a picture using portrait and using landscape orientations in the camera app. The landscape orientation would have a distorted aspect ratio.

The html I set the img tag height and width to 120px and 180px respectively. As you see I used the same values for targetWidth and targetHeight respectively and that resolved my scaling issue. You see I copied the API documentation comments for these two options and added my own interpretation.

The last part of the javascript contains the navigator.camera.getPicture method success and fail functions named getPhoto and onFail respectively.

   /* navigator.camera.getPicture success function */
    function getPhoto(imageData) 
    {
        var cameraImage = document.getElementById('cameraImage');
        cameraImage.src = imageData;
        console_log.innerHTML += "getPhoto() - cameraImage.src: " + cameraImage.src + "<br/>";
    }
    /* navigator.camera.getPicture fail function */
    function onFail(message) 
    {
        alert('Failed because: ' + message);
    }       

For getPhoto the image url is passed in the imageData argument in line 40. All is needed is to assign the url to img tag src property. In the html the img tag has the id of cameraImage and on line 42 we get a reference from the DOM and on line 43 the url assignment to the img tag is complete.

Line 44 displays the url in our makeshift console window for observation.

The navigator.camera.getPicture fail callback is the onFail function on line 47. Line 49 displays feedback using the argument which has a message. I have not yet hit a problem and do not know how to simulate one.

Step 8 – Run the App

When you run the app, you need to use your IPhone or IPad. The camera cannot be simulated. Here is the first screen. At the beginning of the blog are all the screens for your reference.

Good luck!

[ad name=”Google Adsense”]

Categories
Articles

HTML5 Canvas Based Animation Asteriods Space Ship Movement

This is an example of an asteroids game space ship using the HTML5 canvas and JQuery.

Demo

Download Source

The goal here was to reproduce the same example i posted for Actionscript 3 here: Actionscript 3 Animation Following the Mouse to Show Velocity at an Angle

Foundation HTML5 Animation with JavaScript
Learn More

That example as well as this is not a full asteroids game. I am posting it for the basic principles of velocity, acceleration and force. In this case the velocity, direction with speed, is computed for x, y and rotation.

JQuery is used for keystroke capture. Our space ship has a thruster for forward motion and ability to turn left or right. The up key is used for thrusting forward and the left and right keys for the turning as you should expect. The ship enters the opposite side of the canvas if it leaves the canvas.

The ship is a basic triangle and also has a flame when thrusting.

[ad name=”Google Adsense”]
You can view the full html file at the end of the post. To explain what is going on inside excerpts of the code are broken out.

The Canvas and Basic Animation Framework

This snippet defines basic constants and variables to simplify code later.

	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;

The canvas element is created dynamically in JavaScript and appended to the body element. I like to name the canvas drawing context canvas as you can see on line 29.

	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	

These next few snippets represent the animation framework. I have used this in other HTML5 canvas examples.

Three framework functions are init, update and draw. The init function is the initialization of the animation framework and any components. The only components are the canvas and the ship.

For the init method you see some keys being identified for the document to process via JQuery. You could call JQuery that part of the framework.

The init method also initializes the ship object. Without the bounds set, the ship will leave the canvas and may never be seen again.

	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind('keyup', KEY_NAME_THRUSTER, keyEvent); 
		$(document).bind('keydown', KEY_NAME_THRUSTER, keyEvent); 
		// Initialize the ship start position and boundaries.
		ship.x = canvasCenterX;
		ship.y = canvasCenterY;
		ship.bounds.left = 0;
		ship.bounds.right = CANVAS_WIDTH;
		ship.bounds.top = 0;
		ship.bounds.bottom = CANVAS_HEIGHT;
	}

Here is the key processing function. I am taking the JQuery key names and converting them to name I set in constants for more readable code.

The keyEvent function also would call animation object keyEvent methods and here we call the ship object;s keyEvent method.

	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Left arrow key
		if (evt.keyCode == 37)
		{
			keyName = KEY_NAME_LEFT;
		}
		// Right arrow key
		if (evt.keyCode == 39)
		{
			keyName = KEY_NAME_RIGHT;
		}
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_THRUSTER;	
		}
		// Call animation object keyEvent methods.
		ship.keyEvent(evt.type,keyName);
	}

The animation framework update method is called for updating model data. The draw method refreshes the view.

These two methods are called for each frame in the animation.

	// Update the model data.
	function update() 
	{
		ship.update();
	}
	// Draw the view.
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "blue";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "black";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		ship.draw();
	}

This is the heart of the animation. Once JQuery loads the animation framework is initialized and then the timer starts to fire for each frame in the animation.

	// JQuery ready - start here
	$(function() 
	{
		// Initialization.
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});

[ad name=”Google Adsense”]
The Ship Object
The ship object has all the needed data and logic to run in the animation framework.

This first snippet of code show the public and private properties. The underscore is indicate a private property. They should be easy to follow with the comment included or their name.

	var ship = 
	{
		shipStrokeColor: "#8ED6FF",
		shipFillColor: "#0000ff",
		flameStrokeColor: "#ff0000",
		flameFillColor: "#ff0000",
		x: 0,  			// Canvas x.
		y: 0,			// Canvas y.
		leftThrust: -5, // Number of degrees for each left turn request.
		rightThrust: 5, // Number of degrees for each right turn request.
		bounds: {top:-1,bottom:-1,left:-1,right:-1}, // Boundaries. -1 = no boundary.
		_drawStartX: 10,// Drawing starting x.
		_drawStartY: 0,	// Drawing starting y.
		_radians:0,		// Rotation value required for the canvas rotate method.
		_vx:0,			// Velocity for x.
		_vy:0,			// Velocity for y.
		_vr:0,			// Velocity for rotation.
		thrust:.2,		// Incremental force added to acceleration.
		_thrust:0,		// Force added to acceleration.
		rotation:0,		// Degrees of rotation.
		_radians:0,		// Degrees of rotation converted to radians for canvas.rotate method.

This is the ship object’s update method required by the animation framework. Here we get into trigonometry and physics. Its fairly easy to follow when you think we need to compute a velocity for the x and y properties and a rotation for direction. Velocity is direction with speed. Then there is force applied to the x and y velocity when the ship’s thruster is used.

First the direction of the ship is computed on line 57. However we added a velocity for rotation. This is the _vr property and basically represents how much we turn with the left and right keys. The leftThrust and rightThrust properties provide an external interface to _vr. We do not have acceleration with rotation, so you can assume the ship automatically compensates for turning to avoid spinning like a top.

Our x and y velocities are represented by _vx and _vy. They are adjusted by force represented by the thrust property. Lines 61 and 62 compute the acceleration based on thrust for both x and y. Then we apply the acceleration to the x and y velocities on lines 64 and 65.

The last step is to set the drawing x and y properties for the ship. Lines 67 – 70 are for the x position and you can see there is a boundary check. This is repeated for the y property on lines 72 – 75.

		update: function() // Update model data
		{
			// Increase rotation by rotation velocity
			this.rotation += this._vr;
			// Calculate rotation in radians
			this._radians = this.rotation * Math.PI / 180;
			// Acceleration for x and y velocity increases
			var ax = Math.cos(this._radians) * this._thrust;
			var ay = Math.sin(this._radians) * this._thrust;
			// Velocity for x and y
			this._vx += ax;
			this._vy += ay;
			// New x position.
			this.x += this._vx;
			// Check for x boundaries
			if (this.x > this.bounds.right && this.bounds.right >= 0) this.x = this.bounds.left;
			if (this.x < this.bounds.left && this.bounds.left >= 0) this.x = this.bounds.right;
			// New y position.
			this.y += this._vy;
			// Check for y boundaries
			if (this.y > this.bounds.bottom && this.bounds.bottom >= 0) this.y = this.bounds.top;
			if (this.y < this.bounds.top && this.bounds.top >= 0) this.y = this.bounds.bottom;
		},

The draw method is the next animation framework requirement. Here we get into the HTML5 canvas method and techniques to draw the ship with and without a thruster flame.

The procedure is simplified by drawing the ship without regard to its position on the canvas and then translating that to the canvas position. The canvas save and restore methods on lines 80 and 112 respectively give us that ability.

In between the work is done by first specifying the translation point on line 82. Then canvas rotation is done. Then we draw the ship. Line 86 determines if we need to draw a flame for thrusters firing. Finally the ship, a basic triangle, is draw.

Note the beginPath methods on lines 88 and 98. These keep the drawing fresh and distinct. Without them you would see the colors and lines overlap and each time the ship is rotated, you would see the previous ship.

		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas next draw position
			canvas.translate(this.x,this.y);
			// Rotate
			canvas.rotate(this._radians);
			// Thrusters on draw flame
			if (this._thrust > 0)
			{
				canvas.beginPath();
				canvas.moveTo(this._drawStartX - 12.5, this._drawStartY - 5);	
				canvas.lineTo(this._drawStartX - 25 , this._drawStartY);	
				canvas.lineTo(this._drawStartX - 12.5 , this._drawStartY + 5);	
				canvas.fillStyle = this.flameStrokeColor;
				canvas.fill();
				canvas.strokeStyle = this.flameFillColor;		
				canvas.stroke();
			}
			// Begin drawing the ship
			canvas.beginPath();
			// Start.
			canvas.moveTo(this._drawStartX, this._drawStartY);	
			// 	Ship body	
			canvas.lineTo(this._drawStartX -20 , this._drawStartY + 10);		
			canvas.lineTo(this._drawStartX -15 , this._drawStartY);		
			canvas.lineTo(this._drawStartX -20 , this._drawStartY - 10);	
			// Close.			
			canvas.lineTo(this._drawStartX, this._drawStartY);	
			canvas.fillStyle = this.shipStrokeColor;
			canvas.fill();
			canvas.strokeStyle = this.shipFillColor;		
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	},

The last integrating for the ship object into the animation framework is handling the keystrokes.

First take a look back at line 18 and you see we set up constants for the key names and key types. These are translated from the keys pressed in the main framework keyEvent function on line 159 shown earlier.

	// Keyname constants
	var KEY_NAME_THRUSTER = "up";
	var KEY_NAME_LEFT = "left";
	var KEY_NAME_RIGHT = "right";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";

Then the ship keyEvent method needs to evaluate the key name and key type to update data. For example when the left or right key is released, the rotation velocity is set to zero on lines 136 to 140 to zero. This is our automatic adjustment for acceleration when turning. Consider it an advanced feature.

For turning left or right, the rotation velocity is set to the leftThrust and rightThrust properties in lines 126 to 135. This only happens when the key type is down.

For forward motion is covered on lines 121 to 125. Simply the internal thrust property is set with the public interface thrust property. Here we are applying force.

To stop applying forward force, lines 117 to 121 set the internal thrust back to zero.

You can see the

		keyEvent: function(keyType, keyName)
		{
			// Thruster is off
			if (keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_UP)
			{
				this._thrust = 0;
				this._vr = 0;	
			}
			// Thruster off
			else if(keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_DOWN)
			{
				this._thrust = this.thrust;
			}
			// Turning left
			if (keyName == KEY_NAME_LEFT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = this.leftThrust;
			}
			// Turning right
			if (keyName == KEY_NAME_RIGHT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = this.rightThrust;
			}
			// Stop turning
			if ((keyName == KEY_NAME_RIGHT || keyName == KEY_NAME_LEFT ) && keyType == KEY_TYPE_UP)
			{
				this._vr = 0;
			}
		}

[ad name=”Google Adsense”]
The entire HTML document for your convenience to view and copy.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<title>HTML5 Canvas Based Animation Asteriods Space Ship Movement</title>
</head>
<body>
<div><a href="https://www.lonhosford.com/lonblog/2011/10/22/html5-canvas-bobbing-circle-animation-demonstrating-sine-wave-movement/">Article Posted By Lon Hosford - HTML5 Canvas Based Animation Asteriods Space Ship Movement</a>
<p>Use up key to move. Left and right keys to turn.</div>
<script type='text/javascript'>
	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;
	// Keyname constants
	var KEY_NAME_THRUSTER = "up";
	var KEY_NAME_LEFT = "left";
	var KEY_NAME_RIGHT = "right";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";
	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// The ship
	var ship = 
	{
		shipStrokeColor: "#8ED6FF",
		shipFillColor: "#0000ff",
		flameStrokeColor: "#ff0000",
		flameFillColor: "#ff0000",
		x: 0,  			// Canvas x.
		y: 0,			// Canvas y.
		bounds: {top:-1,bottom:-1,left:-1,right:-1}, // Boundaries. -1 = no boundary.
		_drawStartX: 10,// Drawing starting x.
		_drawStartY: 0,	// Drawing starting y.
		_radians:0,		// Rotation value required for the canvas rotate method.
		_vx:0,			// Velocity for x.
		_vy:0,			// Velocity for y.
		_vr:0,			// Velocity for rotation.
		thrust:.2,		// Incremental force added to acceleration.
		_thrust:0,		// Force added to acceleration.
		rotation:0,		// Degrees of rotation.
		_radians:0,		// Degrees of rotation converted to radians for canvas.rotate method.
		update: function() // Update model data
		{
			// Increase rotation by rotation velocity
			this.rotation += this._vr;
			// Calculate rotation in radians
			this._radians = this.rotation * Math.PI / 180;
			// Acceleration for x and y velocity increases
			var ax = Math.cos(this._radians) * this._thrust;
			var ay = Math.sin(this._radians) * this._thrust;
			// Velocity for x and y
			this._vx += ax;
			this._vy += ay;
			// New x position.
			this.x += this._vx;
			// Check for x boundaries
			if (this.x > this.bounds.right && this.bounds.right >= 0) this.x = this.bounds.left;
			if (this.x < this.bounds.left && this.bounds.left >= 0) this.x = this.bounds.right;
			// New y position.
			this.y += this._vy;
			// Check for y boundaries
			if (this.y > this.bounds.bottom && this.bounds.bottom >= 0) this.y = this.bounds.top;
			if (this.y < this.bounds.top && this.bounds.top >= 0) this.y = this.bounds.bottom;
		},
		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas next draw position
			canvas.translate(this.x,this.y);
			// Rotate
			canvas.rotate(this._radians);
			// Thrusters on draw flame
			//if (this._thrust > 0)
			//{
				canvas.beginPath();
				canvas.moveTo(this._drawStartX - 12.5, this._drawStartY - 5);	
				canvas.lineTo(this._drawStartX - 25 , this._drawStartY);	
				canvas.lineTo(this._drawStartX - 12.5 , this._drawStartY + 5);	
				canvas.fillStyle = this.flameStrokeColor;
				canvas.fill();
				canvas.strokeStyle = this.flameFillColor;		
				canvas.stroke();
			//}
			// Begin drawing the ship
			canvas.beginPath();
			// Start.
			canvas.moveTo(this._drawStartX, this._drawStartY);	
			// 	Ship body	
			canvas.lineTo(this._drawStartX -20 , this._drawStartY + 10);		
			canvas.lineTo(this._drawStartX -15 , this._drawStartY);		
			canvas.lineTo(this._drawStartX -20 , this._drawStartY - 10);	
			// Close.			
			canvas.lineTo(this._drawStartX, this._drawStartY);	
			canvas.fillStyle = this.shipStrokeColor;
			canvas.fill();
			canvas.strokeStyle = this.shipFillColor;		
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	},
		keyEvent: function(keyType, keyName)
		{
			// Thruster is off
			if (keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_UP)
			{
				this._thrust = 0;
				this._vr = 0;	
			}
			// Thruster off
			else if(keyName == KEY_NAME_THRUSTER && keyType == KEY_TYPE_DOWN)
			{
				this._thrust = this.thrust;
			}
			// Turning left
			if (keyName == KEY_NAME_LEFT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = -5;
			}
			// Turning right
			if (keyName == KEY_NAME_RIGHT && keyType == KEY_TYPE_DOWN)
			{
				this._vr = 5;
			}
			// Stop turning
			if ((keyName == KEY_NAME_RIGHT || keyName == KEY_NAME_LEFT ) && keyType == KEY_TYPE_UP)
			{
				this._vr = 0;
			}
		}
	};	
	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind('keyup', KEY_NAME_THRUSTER, keyEvent); 
		$(document).bind('keydown', KEY_NAME_THRUSTER, keyEvent); 
		// Initialize the ship start position and boundaries.
		ship.x = canvasCenterX;
		ship.y = canvasCenterY;
		ship.bounds.left = 0;
		ship.bounds.right = CANVAS_WIDTH;
		ship.bounds.top = 0;
		ship.bounds.bottom = CANVAS_HEIGHT;
	}
	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Left arrow key
		if (evt.keyCode == 37)
		{
			keyName = KEY_NAME_LEFT;
		}
		// Right arrow key
		if (evt.keyCode == 39)
		{
			keyName = KEY_NAME_RIGHT;
		}
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_THRUSTER;	
		}
		// Call animation object keyEvent methods.
		ship.keyEvent(evt.type,keyName);
	}
	// Update the model data.
	function update() 
	{
		ship.update();
	}
	// Draw the view.
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "blue";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "black";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		ship.draw();
	}
	// JQuery ready - start here
	$(function() 
	{
		// Initialization.
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});
</script>
</body>
</html>

Categories
Articles

HTML5 Canvas Based Animation Rotate Arrow To Mouse Position

This is an example of using the HTML5 canvas and using JavaScript Math.atan2 to rotate a canvas drawn arrow to point at the mouse position.

Demo

Download Source

Basically I wanted to reproduce the logic for the same example I did in Actionscript 3 which you can read here: Actionscript 3 Animation Following the Mouse to Show Velocity at an Angle

Foundation HTML5 Animation with JavaScript
Learn More

This example required using the save and restore canvas methods to draw an arrow, translate its position to center of the canvas and then rotate. Also I needed the beginPath canvas method to solve a gotcha when using save and restore. Without beginPath, the saved drawing keeps the previous drawing and you end up drawing another arrow on the saved arrow. Makes sense, but the syntax of canvas methods are not intuitive in the first place and those who post about the save and restore methods miss this key point.

I am using the Google CDN for JQuery. JQuery is just standard procedure and its use here is to detect we are ready to go.

This example adds the canvas node to the body dynamically in JavaScript and is just a technique. You could include the canvas tag statically. Also you see that event listeners are added to the canvas for mouse up and mouse move events.
[ad name=”Google Adsense”]
You can view the full html file at the end of the post. I will excerpt parts to explain what is going on inside the code.

The Canvas and Mouse Data Singleton
Line 19 creates a singleton to represent mouse data. This was we can inject it into any other classes or objects that need access to mouse information. Our example is simple and only one object needs access to the mouse data.

The remaining lines in this snippet are dynamically appending the canvas element to the document.

	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;
	// Object to hold mouse information.
	var mouse =
	{
		x:0,
		y:0
	};
	// State for playing or not playing the animations.
	var isPlaying = false;
	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH +
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');

Mouse Event Handling
Mouse up and mouse move event listeners are added to the canvas on lines 34 and 36.

For the mouse up the function called on line 38 toggles the playing state for the interaction.

Line 43 computes the mouse position and updates our mouse object. There are many posts discussing how to capture the mouse position over the canvas. I modified Bibhas Bhattacharya’s code in his article Converting Mouse Event Coordinates to Canvas Coordinate.

	// Add the mousemove event listener to the canvas element.
	canvasElement.get(0).addEventListener("mousemove", mouseMove, false);
	// Add the mouseup event listener to the canvas element.
	canvasElement.get(0).addEventListener("mouseup", mouseUp, false);
	// Handler for mouseMove events. Computes mouseX and mouseY properties.
	function mouseUp(e)
	{
		isPlaying  = !isPlaying;
	}
	// Handler for mouseMove events. Computes mouseX and mouseY properties.
	function mouseMove(e)
	{
		if (e.pageX)
		{
			mouse.x = e.pageX;
		}
			else if (e.clientX)
		{
			mouse.x = e.clientX + document.body.scrollLeft
		  		+ document.documentElement.scrollLeft;
		}
		mouse.x = mouse.x - canvasElement.get(0).offsetLeft;
		if (e.pageY)
		{
			mouse.y = e.pageY;
		}
		else if (e.clientY)
		{
			mouse.y = e.clientY + document.body.scrollTop
		  		+ document.documentElement.scrollTop;
		}
		mouse.y = mouse.y - canvasElement.get(0).offsetTop;
	}

[ad name=”Google Adsense”]

The Arrow Singleton
Starting on line 68 is the arrow object. It is a singleton for our example. The main purpose is to draw an arrow in any direction, or from a geometry perspective, for any angle.

The main animation timer calls the arrow update method on line 79 to compute the needed angle expressed in radians. The distance from the center of the arrow and the position of the mouse are fed to the Math.atan2 method and we get the needed radian value.

The draw method on line 88 is also called with the main animation timer. The arrow is drawn pointing right. The drawing starts on the top left which is the shaft to the arrow. Drawing proceeds around to return to the same point. You can follow the notes in the code for the path taken.

The arrow measurements are such that drawing assumes a 0,0 registration point within the arrow. So you can see the offsets at each point to assure that registration point.

The arrow is drawn separately at position 0,0 for the canvas using these offsets. This position is translated to the center of the canvas and then rotated as shown on lines 93 and 95. To make this happen with 0,0 coordinates, the canvas save and canvas restore methods on lines 91 and 116 are used.

One important step is to include the canvas beginPath method on line 97. Leaving that out still draws the arrow but draws it over the last arrow. The saved state will assume drawing has not ended and each time the draw method is called it assumes the drawing is additive.

Notice on line 77 the arrow has a mouse object.

	// The arrow drawing we are rotating.
	var arrow =
	{
		strokeColor: "#8ED6FF",
		fillColor: "#0000ff",
		x: -50,  	// Starting x
		y: -25,		// Starting y
		_radians:0,	// Rotation value required for the canvas rotate method.
		centerX: 0,	// Center x point on canvas to draw
		centerY: 0,	// Center y point on canvas to draw.
		mouse: {},	// Mouse object
		_dx:0,
		_dy:0,
		update: function()
		{
			// Distance from mouse x and center of canvas.
			this._dx = mouse.x - this.centerX;
			// Distance from mouse y and center of canvas.
			this._dy = mouse.y - this.centerY;
			// Radians for the canvas rotate method.
			this._radians = Math.atan2(this._dy,this._dx);
		},
		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas to center
			canvas.translate(this.centerX, this.centerY);
			// Rotate
			canvas.rotate(this._radians);
			// Create new drawing
			canvas.beginPath();
			// Start point top left of arrow shaft.
			canvas.moveTo(this.x, this.y);
			// Top left of arrow shaft plus top left of arrow head.
			canvas.lineTo(this.x + 50 , this.y);
			canvas.lineTo(this.x + 50, this.y - 25);
			// Arrow point.
			canvas.lineTo(this.x + 100, this.y + 25);
			canvas.lineTo(this.x + 50, this.y + 75);
			// Bottom left of arrow head and bottom left of arrow shaft.
			canvas.lineTo(this.x + 50, this.y + 50);
			canvas.lineTo(this.x, this.y + 50);
			// Close the bottom of arrow shaft.
			canvas.lineTo(this.x, this.y);
			canvas.fillStyle = this.strokeColor;
			canvas.fill();
			canvas.strokeStyle = this.fillColor;
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	}
	};

The update and draw function are the main animation functions. The timer calls them on lines 148 and 149.

Update simply calls the arrow update method when the state isPlaying is true. The update methods are the place to compute new data.

The draw function calls the arrow draw method. The draw methods are for drawing the view with the animation model data.

Lines 141 to 143 initializes the arrow object with its center point on the canvas and the mouse object. These provided necessary data to compute the angle from its center to the mouse position.

	// Update the model data.
	function update()
	{
		if (isPlaying)
		{
			arrow.update();
		}
	}
	// Draw the views
	function draw()
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		arrow.draw();
	}
	// JQuery ready - start here
	$(function()
	{
		// Initialization. Since arrow is a singleton, need to set initalization values.
		arrow.centerX = canvasCenterX;
		arrow.centerY = canvasCenterY;
		arrow.mouse = mouse;
		// Timer for animation.
		setInterval(function()
		{
			update();
			draw();
		}, 1000/FPS);
	});

[ad name=”Google Adsense”]
The entire HTML document for your convenience to view and copy.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<title>HTML5 Canvas Based Animation Rotate Arrow To Mouse Position</title>
</head>
<body>
<div><a href="https://www.lonhosford.com/lonblog/2011/10/22/html5-canvas-bobbing-circle-animation-demonstrating-sine-wave-movement/">Article Posted By Lon Hosford - HTML5 Canvas Based Animation Rotate Arrow To Mouse Position</a>
<p>Click canvas to start and stop. Move mouse to rotate arrow.</div>
<script type='text/javascript'>
	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points.
	var canvasCenterX = CANVAS_WIDTH / 2;
	var canvasCenterY = CANVAS_HEIGHT / 2;
	// Object to hold mouse information.
	var mouse = 
	{
		x:0,
		y:0
	};
	// State for playing or not playing the animations.
	var isPlaying = false;
	// Create html syntax for canvas element.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// Add the mousemove event listener to the canvas element.
	canvasElement.get(0).addEventListener("mousemove", mouseMove, false);
	// Add the mouseup event listener to the canvas element.
	canvasElement.get(0).addEventListener("mouseup", mouseUp, false);
	// Handler for mouseMove events. Computes mouseX and mouseY properties.
	function mouseUp(e)
	{
		isPlaying  = !isPlaying;
	}
	// Handler for mouseMove events. Computes mouseX and mouseY properties.
	function mouseMove(e)
	{
		if (e.pageX)   
		{
			mouse.x = e.pageX;
		} 
			else if (e.clientX)   
		{
			mouse.x = e.clientX + document.body.scrollLeft
		  		+ document.documentElement.scrollLeft;
		}
		mouse.x = mouse.x - canvasElement.get(0).offsetLeft;
		if (e.pageY)   
		{
			mouse.y = e.pageY;
		} 
		else if (e.clientY)   
		{
			mouse.y = e.clientY + document.body.scrollTop
		  		+ document.documentElement.scrollTop;
		}
		mouse.y = mouse.y - canvasElement.get(0).offsetTop;
	}
	// The arrow drawing we are rotating.
	var arrow = 
	{
		strokeColor: "#8ED6FF",
		fillColor: "#0000ff",
		x: -50,  	// Starting x
		y: -25,		// Starting y
		_radians:0,	// Rotation value required for the canvas rotate method.
		centerX: 0,	// Center x point on canvas to draw
		centerY: 0,	// Center y point on canvas to draw.
		mouse: {},	// Mouse object
		_dx:0,
		_dy:0,
		update: function()
		{
			// Distance from mouse x and center of canvas.
			this._dx = mouse.x - this.centerX; 
			// Distance from mouse y and center of canvas.
			this._dy = mouse.y - this.centerY; 
			// Radians for the canvas rotate method.
			this._radians = Math.atan2(this._dy,this._dx);
		},
		draw: function() // Draw.
		{
			// Draw off canvas
			canvas.save();
			//Translate canvas to center
			canvas.translate(this.centerX, this.centerY);
			// Rotate
			canvas.rotate(this._radians);
			// Create new drawing
			canvas.beginPath();
			// Start point top left of arrow shaft.
			canvas.moveTo(this.x, this.y);	
			// Top left of arrow shaft plus top left of arrow head.			
			canvas.lineTo(this.x + 50 , this.y);		
			canvas.lineTo(this.x + 50, this.y - 25);			
			// Arrow point.
			canvas.lineTo(this.x + 100, this.y + 25);		
			canvas.lineTo(this.x + 50, this.y + 75);	
			// Bottom left of arrow head and bottom left of arrow shaft.
			canvas.lineTo(this.x + 50, this.y + 50);	
			canvas.lineTo(this.x, this.y + 50);
			// Close the bottom of arrow shaft.			
			canvas.lineTo(this.x, this.y);	
			canvas.fillStyle = this.strokeColor;
			canvas.fill();
			canvas.strokeStyle = this.fillColor;		
			canvas.stroke();
			// Put it on the canvas
			canvas.restore();
	  	}
	};	
	// Update the model data.
	function update() 
	{
		if (isPlaying)
		{
			arrow.update();
		}
	}
	// Draw the views
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		arrow.draw();
	}
	// JQuery ready - start here
	$(function() 
	{
		// Initialization. Since arrow is a singleton, need to set initalization values.
		arrow.centerX = canvasCenterX;
		arrow.centerY = canvasCenterY;
		arrow.mouse = mouse;
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});
</script>
</body>
</html>

Categories
Articles

HTML5 Canvas Bobbing Circle Animation Demonstrating Sine Wave Movement

This is an example of using the HTML5 canvas and using JavaScript Math.sin to produce a bobbing animation of a circle.

Demo

Download Source

This example is in one HTML document that contains all the JavaScript.

Foundation HTML5 Animation with JavaScript
Learn More

JQuery is included only to detect when the document is ready and start the timer for the animation. The timer calls an update function which updates the data values for the items on the canvas. In this case it is just the animated object. The timer also calls the draw function that calls the animated object’s draw function.

I am using the Google CDN for JQuery.

This example adds the canvas node to the body dynamically in JavaScript and is just a technique. You could include the canvas tag statically.

You can view the full html file at the end of the post. I will excerpt parts to explain what is going on inside the code.

[ad name=”Google Adsense”]

Once JQuery is ready the animation timer is started. The timer calls the update and draw methods.

	// JQuery ready
	$(function() 
	{
		// Start here.
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});

This is the draw method. It simply clears the canvas, draws the canvas background and border, and then calls the draw method of the animation object called player.

	// Draw the views
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		player.draw();
	}

The model animation data is maintained in the update function. There is the angle and the player x and y positions to update.

The player x value is the horizontal center of the canvas. That does not change in this example.

Line 57 computes the player y value. The values of 1 to -1 are computed from the angle using the Math.sin function. The angle variable increments infinitely. The Math.sin function uses the angle values as real possible angles in geometry you are accustomed to using.

The yRange value of 200 multiplied by Math.sin(angle) results in values between -200 and 200.

Adding the result to canvasCenterY, the vertical center of the canvas, creates the y value plus or minus the vertical center of the canvas.

	// Update the model data.
	function update() 
	{
		// Coordinate for x is center canvas width.
		player.x = canvasCenterX;
		// Coordinate y is the sine of the angle 
		// for a positive or negative percentage of the range 
		// using the canvas vertical for the center of the range.
		player.y = canvasCenterY + Math.sin(angle) * yRange;
		// Angle changed by speed.
		angle += speed;
	}

[ad name=”Google Adsense”]
This is the object that is being animated. It contains some basic properties for a circle and contains a method for drawing.

	var player = {
		color: "#00A",
		x: 0,  	// Starting x
		y: 0,	// Starting y
		width: 32,
		height: 32,
		radius: 20,
		draw: function() // Draw the player.
		{
			canvas.fillStyle = this.color;		
			canvas.beginPath();  
			canvas.arc(this.x,this.y,this.radius,0,Math.PI*2,true); 
			canvas.fill();
			canvas.lineWidth = 5;
    		canvas.strokeStyle = "black";
    		canvas.stroke();
	  	}
	};	

The entire HTML document for your convenience to view and copy.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<title>HTML5 Canvas Based Animation Sine Wave Movement Using Bobbing Circle</title>
</head>
<body>
<script type='text/javascript'>
	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points adjusted for stroke.
	var canvasCenterX = (CANVAS_WIDTH - 1) / 2;
	var canvasCenterY = (CANVAS_HEIGHT - 1) / 2;
	// Speed of movement
	var speed = .05;
	// Angle for computing cosine and sin.
	var angle = 0;
	// Range of the vertical movement.
	var yRange = 200;
	// Create a canvas element variable.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// Object defining the player we are moving.
	var player = {
		color: "#00A",
		x: 0,  	// Starting x
		y: 0,	// Starting y
		width: 32,
		height: 32,
		radius: 20,
		draw: function() // Draw the player.
		{
			canvas.fillStyle = this.color;		
			canvas.beginPath();  
			canvas.arc(this.x,this.y,this.radius,0,Math.PI*2,true); 
			canvas.fill();
			canvas.lineWidth = 5;
    		canvas.strokeStyle = "black";
    		canvas.stroke();
	  	},
	};	
	// Update the model data.
	function update() 
	{
		// Coordinate for x is center canvas width.
		player.x = canvasCenterX;
		// Coordinate y is the sine of the angle 
		// for a positive or negative percentage of the range 
		// using the canvas vertical for the center of the range.
		player.y = canvasCenterY + Math.sin(angle) * yRange;
		// Angle changed by speed.
		angle += speed;
	}
	// Draw the views
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		player.draw();
	}
	// JQuery ready
	$(function() 
	{
		// Start here.
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
	});
</script>
</body>
</html>

Categories
Articles

HTML5 Canvas Circular Rotation Animation Example Using JQuery Hotkeys For User Interaction

This takes my first example animating a filled circle rotating around the center point to a user interaction level using JQuery Hotkeys. JQuery Hotkeys is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination using JQuery.

Foundation HTML5 Animation with JavaScript
Learn More

This version of the animation allows for start and stop playing. It also includes reversing the direction of rotation from clockwise to counter clockwise and back again. Finally I decided to allow increasing and decreasing the speed of rotation.

Another change from the previous example is to move as much relevant functionality as possible into the animated object called player. For example the player object is responsible for its own data computations and for interpreting key events.

The events in the animation are the timer and key events. The timer event calls the update and draw functions at the desired frame rate. The update and draw functions then call the same functions for all animation objects: we only have one animation object at this point.

Demo

Download

[ad name=”Google Adsense”]
This is the html5 head section. Line 4 brings in JQuery from the Google CDN. Line 5 references a local copy of JQuery Hotkeys. In this case the 0.7.9 minimized version.

The html structure is expanded to include elements for a main title, a subtitle and keyboard instructions for the animation. The internal style on lines 9 – 27 provide formatting for these new structural elements.

<!DOCTYPE HTML>
<html>
<head>
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<script src="js/jquery.hotkeys-0.7.9.min.js"></script>
<title>HTML5 Canvas Based Animation Circular Movement Start, Stop, Reverse, Faster, Slower</title>
<meta charset="UTF-8">
<title>Untitled Document</title>
<style type="text/css">
	body {
		font-family:Arial, Helvetica, sans-serif;
		font-size:12px;
	}
	#titleDiv{
		font-family:Verdana, Geneva, sans-serif;
	}
	#titleHeadDiv{
		font-size:16px;
	}
	#titleSub1Div{
		font-size:14px;
	}
	#instructionsDiv{
		margin-top:10px;
		margin-bottom:5px;	
	}
</style>
</head>
<body>
<div id="titleDiv">
    <div id="titleHeadDiv">HTML5 Canvas Based Animation Circular Movement</div>
    <div id="titleSub1Div">Key Controlled Start, Stop, Reverse, Faster, Slower</div>
</div>
<div id="instructionsDiv">Keys: P=Play, S=Stop, R=Reverse, Up=Faster, Down=Slower</div>

<script type='text/javascript'>

To keep the code readable, lines 38 – 57 define constants.

Line 54 – 68 are dynamically appending the canvas element to the body element. Then the canvas variable on line 61 represents the drawing context for the code.

// Canvas width
	var CANVAS_WIDTH = 480;	
	// Canvas height	
	var CANVAS_HEIGHT = 480;	
	// Rotation direction constants
	var CLOCKWISE = 'clockwise';
	var COUNTER_CLOCKWISE = 'counter_clockwise';
	// Key name constants
	var KEY_NAME_PLAY = 'p';
	var KEY_NAME_STOP = 's';
	var KEY_NAME_REVERSE = 'r';
	var KEY_NAME_FASTER = "up";
	var KEY_NAME_SLOWER = "down";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";
	var FPS = 30;				// Frames per second
	// Canvas center points adjusting for stroke.
	var canvasCenterX = (CANVAS_WIDTH - 1) / 2;
	var canvasCenterY = (CANVAS_HEIGHT - 1) / 2;
	// Create a canvas element variable.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// Object defining the player we are rotating.

[ad name=”Google Adsense”]
The player object is the bulk of the code to study.

Lines 65 – 77 define a series of properties for the player object. The player simply rotates around a point.

This example aimed at making the animation objects more independent. For example the speed property allows an independent speed.

	var player = {
		color: "#00A",
		x: 0,  		// Starting x
		y: 0,		// Starting y
		radius: 20,	// Radius of the player
		direction: CLOCKWISE,	// CLOCKWISE or COUNTER_CLOCKWISE
		angle:0,				// Degrees of rotation for sin and cosine
		speed: .05,				// Pixels per frame
		speedChange: .001,		// Pixels per frame
		speedMax: .18,			// Pixels per frame
		speedMin: .005,			// Pixels per frame
		radiusRotation:200,		// Rotation circle radius
		isPlaying: false,		// True false state animating

The player draw method is called from the main animation loop. The method uses the player object’s properties to draw the player. One bare number for lineWidth could have been added to properties.

		draw: function() // Draw the player.
		{
			canvas.fillStyle = this.color;		
			canvas.beginPath();  
			canvas.arc(this.x,this.y,this.radius,0,Math.PI*2,true); 
			canvas.fill();
			canvas.lineWidth = 5;
    		canvas.strokeStyle = "black";
    		canvas.stroke();
	  	},

The init method for the player object computes the starting x and y positions. The main animation init function calls the player init method.

		init: function()
		{
			this.x = canvasCenterX + Math.cos(this.angle) * this.radiusRotation;
			this.y = canvasCenterY + Math.sin(this.angle) * this.radiusRotation;
		},

Handling keyboard input is central to this example. The main code captures the key input using JQuery Hotkeys. There the player object keyEvent method is called. It receives the keyType representing either a key down or key up state and the name of the key.

The key down state is not checked but is the state for the play, stop, faster and slower key names. The key down state causes JQuery Hotkeys to repeat the key while it is down. This is acceptable for the play and stop key although we might want to limit those to a key up only state as you will see we did for the reverse key. The faster and slower keys we want to accept repetition if they are held down as we would with an accelerator or brake.

For the reverse key, the key up state is used. If the key down state was used, the reverse key would repeat creating a chaotic result for direction.

		keyEvent: function(keyType, keyName)
		{
			// Set to playing state.
			if (keyName == KEY_NAME_PLAY)
			{
				this.isPlaying = true;
			}
			// Set to stopped state
			else if (keyName == KEY_NAME_STOP)
			{
				this.isPlaying = false;
			}
			// Increase speed if playing
			else if (keyName == KEY_NAME_FASTER && this.isPlaying)
			{
				this.speed += this.speedChange;
				this.speed = Math.min(this.speedMax, this.speed);
			}
			// Decrease speed if playing
			else if (keyName == KEY_NAME_SLOWER && this.isPlaying)
			{
				this.speed -= this.speedChange;
				this.speed = Math.max(this.speedMin, this.speed);
			}
			// Up state of the key.
			if (keyType == KEY_TYPE_UP)
			{
				// Change from clockwise to counter clockwise rotation
				if (keyName == KEY_NAME_REVERSE && this.direction == CLOCKWISE)
				{
					player.direction = COUNTER_CLOCKWISE;
				}
				// Change from counter clockwise rotation to clockwise rotation
				else if (keyName == KEY_NAME_REVERSE && this.direction == COUNTER_CLOCKWISE)
				{
					player.direction = CLOCKWISE;
				}
			}			
		},

The update method handles computation of the model data for the player object. Assuming the player is moving, identified by the isPlaying property, the new position on the rotation perimeter is computed using standard trigonometry.

Also the direction is handled by simply incrementing or decrementing the angle property.

		update: function()
		{
			// Update player data model if playing.
			if (this.isPlaying)
			{
				// Compute the triangle coordinates from the center of rotation
				player.x = canvasCenterX + Math.cos(this.angle) * this.radiusRotation;
				player.y = canvasCenterY + Math.sin(this.angle) * this.radiusRotation;
				// Keep moving the rotation clockwise.
				if (this.direction == CLOCKWISE)
				{
					this.angle += this.speed;
				}
				// Keep moving the rotation counter clockwise.
				else if(this.direction == COUNTER_CLOCKWISE)
				{
					this.angle -= this.speed;
				}
			}
		}
	};	

This is the main animation init function. The task is to initialize all components. The JQuery Hotkeys are initialized here. The player object has its own init method.

The JQuery Hotkeys for the game are defined here. This is the syntax to listen for key events. Notice the distinction for listening to key down and key up states for each key. As mentioned above the key down states repeat so were useful for all but the reverse key where the key up state was captured.

	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_PLAY, keyEvent); 
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_STOP, keyEvent); 
		$(document).bind(KEY_TYPE_UP, KEY_NAME_REVERSE, keyEvent); 	
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_FASTER, keyEvent); 
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_SLOWER, keyEvent); 
		player.init();
	}

[ad name=”Google Adsense”]
The init function registers the keyEvent function for the keys JQuery Hotkeys processes.

We do a bit of conversion of the received event information to make it more readable to our code by creating a key name literal from the keyCode event property.

The final step is to call the keyEvent method on our only player object and it handles the key information accordingly.

	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_FASTER;	
		}
		// Down arrow key
		if (evt.keyCode == 40)
		{
			keyName = KEY_NAME_SLOWER;	
		}
		// Call animation object keyEvent methods.
		player.keyEvent(evt.type,keyName);
		
	}

The main update and draw functions are called with the animation timer.

The update function recomputes the model data. In this case there is just updating the player model data.

For the draw method, the canvas draw operations are included and then the player draw method is called.

The separation of these two allows for a different timing solution that would separate the processing timing of the data and the drawing.

	// Update the model data.
	function update() 
	{  
		// Call the animation object update events.
		player.update();
	}
	// Draw the views
	function draw() 
	{
		//Canvas object clear and draw background and border.
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		// Call the animation object update events.
		player.draw();
	}

This is were the animation starts. Once JQuery fires its ready function, the animation init method is called and then a timer interval is calls the update and draw functions to drive the animation.

	// JQuery ready
	$(function() 
	{
		// Start here.
		
		// Initialize components
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
			
	});

</script>
</body>
</html>

Categories
Articles

HTML5 Canvas Circular Rotation Animation Example

Started dabbling with the HTML5 canvas to create animations. This example animates a filled circle rotating around the center point. I kept the example as simple as possible while anticipating some of the coding design required for a more complex build.

A build on this example that accepts user keyboard input to control the animation play, start, stop, direction and speed is available in this follow up post.

Foundation HTML5 Animation with JavaScript
Learn More

One item that is more sophisticated is that the canvas tag is dynamically created in the Javascript code on lines 35 – 40. This is not necessary as the tag could be inserted into the html and referenced with an id.

Another is including JQuery from the Google CDN. Listening to the body onLoad method is a substitute.

Also I included the script tag after the html content. The script tag could go in the head section. Placing after the html in the body tag allows html content to load and style before the script code is parsed.

Demo

Source

This is the html5 head section. There is an internal style on lines 8 – 17 for the html in the document. Line 4 brings in JQuery from the Google CDN. Line 20 is the display title for the page.

<!DOCTYPE HTML>
<html>
<head>
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<title>HTML5 Canvas Circular Movement Based Animation</title>
<meta charset="UTF-8">
<title>Untitled Document</title>
<style type="text/css">
	body {
		font-family:Arial, Helvetica, sans-serif;
		font-size:12px;
	}
	#titleDiv{
		font-family:Verdana, Geneva, sans-serif;
		font-size:16px;
	}
</style>
</head>
<body>
<div id="titleDiv">HTML5 Canvas Circular Movement Based Animation</div>
<script type='text/javascript'>

The animation key variables are definite and initialized here to keep as many bare numbers out of the code as possible.

The canvas has a border. So there is some computational needs on lines 26-27 to take the border out in computing the center point. May not make a difference in this example, but is helpful for a future example where that boundary is important.

	var CANVAS_WIDTH = 480;		// Canvas width
	var CANVAS_HEIGHT = 480;	// Canvas height
	var FPS = 30;				// Frames per second
	// Canvas center points adjusting for stroke.
	var canvasCenterX = (CANVAS_WIDTH - 1) / 2;
	var canvasCenterY = (CANVAS_HEIGHT - 1) / 2;
	// Radius of the rotation
	var radius = 200;
	// Speed of movement
	var speed = .05;
	// Angle for computing cosine and sin.
	var angle = 0;
	// Create a canvas element variable.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');

[ad name=”Google Adsense”]
This defines the object that moves in the circular rotation. The name player is arbitrary but hints at a way to define objects for a game.

Later we will see the animation loop. The animation loop call the draw function of the items in the animation. So you see the draw function for the player on line 49. This is how to draw a filled circle with a border using the html5 canvas api.

Additionally each object has an init function that is called to initialize the animation objects. For this example the init function for the player object is empty. It could be used for an initial placement of an object for example.

Line 59 shows the master init function that would call all necessary component init functions.

	
	// Object defining the player we are rotating.
	var player = {
		color: "#00A",
		x: 0,  	// Starting x
		y: 0,	// Starting y
		width: 32,
		height: 32,
		radius: 20,
		draw: function() // Draw the player.
		{
			canvas.fillStyle = this.color;		
			canvas.beginPath();  
			canvas.arc(this.x,this.y,this.radius,0,Math.PI*2,true); 
			canvas.fill();
			canvas.lineWidth = 5;
    		        canvas.strokeStyle = "black";
    		        canvas.stroke();
	  	},
		init: function()
		{
		}
	};	
	// Initialize game
	function init()
	{
		player.init();
	}

[ad name=”Google Adsense”]
The update function updates the model data. The animation timer will call this for each animation interval.

Primarily the computations are a right triangle from the center point to the place for the player object to be drawn based on the current angle. This is the x and y where the leg and hypotenuse are based on the angle.

	// Update the model data.
	function update() 
	{  
		// Compute the triangle coordinates from the center of rotation
		player.x = canvasCenterX + Math.cos(angle) * radius;
		player.y = canvasCenterY + Math.sin(angle) * radius;
		// Keep moving the rotation. Use -= for reverse rotation.
		angle += speed;
	}

This is the view being drawn. It draws anything that has to do with the overall canvas. However you could make that a class or object like player and give it a draw method.

Line 85 shows the player draw method being called.

	// Draw the views
	function draw() 
	{
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		player.draw();
	}

[ad name=”Google Adsense”]
This is the start of the script and the heart of the animation. Once JQuery is ready the animation init function is called. Next a Javascript interval is started to call the animation update method to update the data and then the animation draw method to redraw the view.

	// JQuery ready
	$(function() 
	{
		// Start here.
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
			
	});

</script>
</body>
</html>