Following on from my previous posts on html5 animation and reflection effects, I thought I'd put it all together and create a carousel.

image_4_70E15ADC[1]

Firstly, I create a JavaScript pseudo class called CarouselImage. This takes an image url and draws it at a given co-ordinate, scaled by a co-efficient (used to simulate perspective). This class also applies a reflection effect (as per my previous post).

function CarouselImage(url, x, y, scale) {
    this.imageUrl = url;
    this.xPosition = x;
    this.yPosition = y;
    this.scaleCoeff = scale;
 
    this.DrawImageWithReflection = function(currentContext) {
        var mainImage = new Image();
        mainImage.src = this.imageUrl;
        var xPos = this.xPosition;
        var yPos = this.yPosition;
        var scale = this.scaleCoeff;
 
        mainImage.onload = function () {
            var imgWidth = mainImage.width * scale;
            var imgHeight = mainImage.height * scale;
 
            // Draw main image
            currentContext.drawImage(mainImage, xPos, yPos, imgWidth, imgHeight);
 
            // Setup a reflection (via reversing scale in y-direction around an axis that is two times the height of the image)  
            currentContext.translate(0, yPos + (2 * imgHeight));
            currentContext.scale(1, -1);
            currentContext.drawImage(mainImage, xPos, 0, imgWidth, imgHeight);
 
            // Revert transform and scale  
            currentContext.translate(0, yPos + (2 * imgHeight));
            currentContext.scale(1, -1);
 
            // Reflection image overlay, to created fade out effect (solid colour with increasing opacity).
            var alphaGradient = currentContext.createLinearGradient(xPos, yPos + imgHeight, xPos, yPos + (2 * imgHeight));
            alphaGradient.addColorStop(0, "rgba(255, 255, 255, 0.75)");
            alphaGradient.addColorStop(0.75, "rgba(255, 255, 255, 1)");
            currentContext.fillStyle = alphaGradient;
            currentContext.fillRect(xPos - 1, yPos + imgHeight + 1, imgWidth + 2, imgHeight + 2); // Rectangle made 1px bigger in all directions otherwise scaling leave artifacts
        }
    }
}

I then create a pseudo class to calculate the position of images in the carousel. I work out the angle of separation between each image by dividing 360 degrees (2 * Math.PI) by the number of images . I use sine and cosine functions to calculate the image positions on an oval (with co-efficients so that the oval is wider than it is tall to create perspective). I finally scale images, so that images higher up the page are smaller, thus appearing further away. 

function CarouselPosition(centerX, centerY, ovalWidth, ovalHeight) {
    this.Angle = 0;
    this.CenterX = centerX;
    this.CenterY = centerY;
    this.OvalWidth = ovalWidth;
    this.OvalHeight = ovalHeight;
 
    this.CalculateDivisions = function (numberOfItems) {
        this.Angle = (2 * Math.PI / numberOfItems);
    }
 
    this.CalculateXPosition = function (itemPosition, offsetAngle) {
        return this.OvalWidth * Math.sin((this.Angle * itemPosition) + offsetAngle) + this.CenterX;
    }
 
    this.CalculateYPosition = function (itemPosition, offsetAngle) {
        return this.OvalHeight * Math.cos((this.Angle * itemPosition) + offsetAngle) + this.CenterY;
    }
 
    this.CalculateScale = function (itemPosition, offsetAngle) {
        return 1 - (0.2 * -Math.cos((this.Angle * itemPosition) + offsetAngle)) - 0.2;
    }
}

I create a top level class is called Carousel. This takes an array of image urls and plots them at calculated locations via the above classes. An important point to note is that as setting the z-position does not appear to be possible in the Canvas element, I work out all of the images positions and store in array before plotting them . This allows me to sort based on y-position, so images higher up the page appear at the back of the carousel (thus simulating z-position).

function Carousel(currentContext, images) 
{
    this.drawingContext = currentContext;
    this.carouselItems = images;
    this.offsetAngle = 0;
 
    this.DrawCarousel = function () {
 
        // Clear the screen
        this.drawingContext.clearRect(0, 0, 1000, 800);
 
        // Set the position of the caraousel and calculate the angle between each image
        var position = new CarouselPosition(400, 200, 350, 150);
        position.CalculateDivisions(this.carouselItems.length);
              
        // Update offset angle - this is updated each frame for animation
        this.offsetAngle += 0.01;  
              
        // We first calculate the positions of all images and put into an array
        var zSortedArray = new Array();
        for (i = 0; i < this.carouselItems.length; i++) {
            var x = position.CalculateXPosition(i, this.offsetAngle);
            var y = position.CalculateYPosition(i, this.offsetAngle);
            var scale = position.CalculateScale(i, this.offsetAngle);
            var imgUrl = this.carouselItems[i];
 
            var currentImage = new CarouselImage(imgUrl, x, y, scale);
            zSortedArray.push(currentImage);
        }
               
        // Sort by y position (to simulate z-index)
        zSortedArray.sort(this.PeformSort);
 
        // Draw images that have been sorted, so images higher on the screen appear in the background
        for (i = 0; i < zSortedArray.length; i++) {
            var image = zSortedArray[i];
            image.DrawImageWithReflection(this.drawingContext);
        }
    }
 
    this.PeformSort = function(a, b) {
        if (a.yPosition > b.yPosition) {
            return 1;
        }
 
        return -1;
    }
}

In the page onload event, I create an instance of my Carousel object, passing in an array of images paths (these make up the nodes on the carousel).  I finally set a setInterval method to redraw the canvas every 40ms (i.e. to produce 25 frames per second).

this.window.onload = function () {
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
          
    var images = new Array("images/image1.jpg", "images/image2.jpg", "images/image3.jpg", "images/image4.jpg", "images/image5.jpg", "images/image6.jpg", "images/image7.jpg", "images/image8.jpg")
    this.carousel = new Carousel(context, images);
    setInterval('this.carousel.DrawCarousel();', 40);
}  

 

Note that at present, I haven’t set up any event handling (e.g. responding to mouse click, or arrow key presses).

Also note that I have tested this on IE9, Firefox and Chrome. It runs smoothly on IE9, but there’s a reasonable amount of flicker on the other two.

Click here to see it in action.

Please feel free to get in contact if you have any questions.

Written by Rob Nowik