Friday, June 22, 2012


Carousel Behavior using HTML5 Canvas



For one of my personal project I required carousel behavior for my Main page.
The project uses web technologies and I was interested in Canvas. I thought to share my findings.

       Let me brief this.

           1) Hold the images in a hidden control (Can be avoided)
           2) Call a function that draws the image to canvas using a timer. The function will be called as long as the timer is on.
           3)  While drawing calculate the coordinates and keep it in a cache
           4)  During mouseover - Stop the timer and make sure to draw on canvas one time.
           5)  During mousemove - find the mouse pointer location by reading the coordinates kept in cache.
          
          

It is time to get in to the details.







I loaded 4 images inside a div tag and hide that dive tag. The HTML will look as below
<body>     <div style="width:automargin-left:500">
        <canvas id="myCanvas" width="600" height="480" >
        </canvas>            </div>
    <input type="button" value="Click" onclick="init();"/>
    <div style="display:none"> 
    <img id="img1" src="Chrysanthemum.jpg" height="100" width="100"  />
    <img id="img2" src="Desert.jpg"  height="100" width="100" />
    <img id="img3" src="Hydrangeas.jpg"  height="100" width="100" />
    <img id="img4" src="Tulips.jpg"  height="100" width="100"/>
</div>
</body>
Apart from images  HTML contains canvas and button used to trigger the carousel aciton.
Reset of the work is done using javascript.


I used below global variables
        var angle = 0;
        var x = 0;
        var y = 0;
        var imgArray = new Array();
        var timerInterval;
        var canvas;
        var imgWidth = 50;
        var imgHeight = 60;
        var canExecuteMouseDown = false;
        var imgIndex = -1;

Let us start with function "init". It does following steps.

         1) Add event handlers to canvas.
         2) Store images in an Array
         3) start the timer.
Here is the code
       function init() {
            canvas = document.getElementById("myCanvas");
            canvas.addEventListener('mouseover', stopAnimation, false); //Event handler
            canvas.addEventListener('mousemove', canvasMouseMove, false); //Event handler
            canvas.addEventListener('mouseout', startAnimation, false); //Event handler
            canvas.addEventListener('mousedown', canvasMouseDown, false); //Event handler
            //Store image in an Array
            var img = new Object();
            img.Image = document.getElementById("img1");
            imgArray.push(img);
            img = new Object();
            img.Image = document.getElementById("img2");
            imgArray.push(img);
            img = new Object();
            img.Image = document.getElementById("img3");
            imgArray.push(img);
            img = new Object();
            img.Image = document.getElementById("img4");
            imgArray.push(img);
 
            //Start Timer
            startAnimation();
            
        }

The below functions are called at various mouse event handlers.
 

function startAnimation() {
            timerInterval = setInterval(draw, 1000 / 30);
        }

function stopAnimation() {
            clearInterval(timerInterval);
        }

        
function canvasMouseDown(evt) {
            if (canExecuteMouseDown)
                alert("You clicked on image " + imgIndex + " . Image name " + imgArray[imgIndex].Image.src);
        }
function canvasMouseMove(evt) {
            var mousePos = getMousePos(canvas, evt);
            imgIndex = findMouseIntersectedObjectIndex(mousePos);
            drawWhenMouseOver(imgIndex);
                
        }

Let us see how to find a mouse is over a object.
         Below are the steps
1) Loop through the cached coordinates (Caching is done in draw function and will be shown below)
2) Check the mouse pointer is inside the rectangle coordinate.
3) If so we return that cache index. That index can used to get the image from imgArray (another cache)


//This function is used to find the mouse pointer on object.
        //This function is used to find the mouse pointer on object.
        function findMouseIntersectedObjectIndex(mousePos) {
            for (i = 0; i < imgArray.length; i++) {
                var img = imgArray[i];
                if (mousePos.x > img.X && mousePos.x < (img.X + imgWidth) && mousePos.y > img.Y && mousePos.y < (img.Y + imgHeight)) {
                    canExecuteMouseDown = true;
                    return i;
                }
            }
            canExecuteMouseDown = false;
            return -1;
        }



We need a function to draw only once when the mouse is over the canvas.The function will be called only once. In order to indicate the mouse is over an image, the width and height will be slightly increased, so that object will look bigger.

//This function will be called when the mouse is over the canvas.
        //This function is to draw the objects on the canvas only one time.
        function drawWhenMouseOver(selectedImgIdx) {
            var ctx = canvas.getContext("2d");
            ctx.clearRect(0, 0, 480, 480); // clear canvas
            var idx = 0;
            for (i = 0; i < 360; i += 90) {
                ctx.beginPath();
                img = imgArray[idx];
                if (selectedImgIdx == idx) {
                    ctx.drawImage(img.Image, img.X, img.Y, imgWidth * img.Scale + 20, imgHeight * img.Scale + 20);
                    
                }
                else {
 
                    ctx.drawImage(img.Image, img.X, img.Y, imgWidth * img.Scale, imgHeight * img.Scale);
                }
                ctx.closePath();
                idx++;
            }
            
        }




Here come the main function that create the rotation of images. In this function we use global variable "angle" that increases linearly from 0 to 360. We find x cordinate and y cordinate using sin and cos function. Here is the formulat to find the X cordinate
  x = centerPoint +   cos(angle) * hyptenuse;
  y =  centerPoint +   sin(angle) * hyptenuse; In order to get an ellipse shape the hypotenuse is different
 



//This function is used to produce circular motion.
        //1) Clear the canvas area 
        //2) Draw the objects using updated co-ordinates.
        //3) increment the angle
        function draw() {
            var ctx = canvas.getContext("2d");
            ctx.clearRect(0, 0, 480, 480); // clear canvas
            var idx = 0;
            //Position the images on the circle at various angles.
            //In this example we have 4 images. Hence keeping that at 0, 90, 180 and 270
            for (i = 0; i < 360; i+=90) { 
                ctx.beginPath();
                x = (480 / 2) + Math.cos(degToRad(angle + i)) * 180; //Calculate the x cordinate on the circle
                y = (480 / 2) + Math.sin(degToRad(angle + i)) * 150; //Calculate the y cordinate on the circle
                scale = y / 390; //Calculate scale value. This is used to show an effect so that far objects will be smaller and closer objects will be bigger. The value range from 0.5 to 1
                if (scale == 0)
                    scale = 0.5;
                img = imgArray[idx++];
                img.X = x;
                img.Y = y;
                img.Scale = scale;
                ctx.drawImage(img.Image, x, y, imgWidth * scale, imgHeight * scale);
                ctx.closePath();
            }
            angle = angle + 1;
            if (angle > 359)
                angle = 0;
        }

 /Convert angle to radian.
        //Javascript Math.Sin and Math.Cos expect argument as radian
        function degToRad(deg) {
            return deg * Math.PI / 180;
        }

Thanks to Eric Rowell for illustrating a wonderful function to get the Mouse position. The method is available from  website "http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/".  The function name is "getMousePos"

I hope this article will be useful.

1 comment: