Wednesday, December 25, 2013

JavaScript in Practice (1) - Cartoon Snow Animation

Christmas is a season about snow and Santa Claus. Let's do the same thing with JavaScript: Animate snow on the background image about Santa Claus is delivering presents to children, as shown in the following picture.
If you are interested in what the animation looks like, please go the link http://jsfiddle.net/zhedahht/2uD5x/1/. The source code is shared at https://github.com/zhedahht/JsInPractice/tree/master/MerryChristmas. The following is the explanation of the code.

First, let's have a look about the definition of snow, as listed below:

function Snow(snowSettings) {
    this.snowSettings = snowSettings;

    this.radius = randomInRange(snowSettings.radiusRange);
    this.initialX = Math.random() * snowSettings.maxX;
    this.y = Math.random() * snowSettings.maxY;
    this.speedY = randomInRange(snowSettings.speedYRange);
    this.speedX = snowSettings.speedX;
    this.alpha = randomInRange(snowSettings.alphaRange);
    this.angle = Math.random(Math.PI * 2);
    this.x = this.initialX + Math.sin(this.angle);
    this.moveX = randomInRange(snowSettings.moveXRange);
}

Snow.prototype.render = function(canvasContext) {
    canvasContext.fillStyle = "rgba(255, 255, 255, " + this.alpha + ")";
    canvasContext.beginPath();
    canvasContext.arc(this.x, this.y, this.radius, 0 ,Math.PI * 2, true);
    canvasContext.closePath();
    canvasContext.fill();
}

Snow.prototype.update = function() {
    this.y += this.speedY;
    if (this.y > this.snowSettings.maxY) {
        this.y -= this.snowSettings.maxY;
    }

    this.angle += this.speedX;
    if (this.angle > Math.PI * 2) {
        this.angle -= Math.PI * 2;
    }

    this.x = this.initialX + this.moveX * Math.sin(this.angle);
}

function randomInRange(range) {
    var random = Math.random() * (range.max - range.min) + range.min;
    return random;
}

We can see that a piece of snow is rendered as a circle, in somewhat transparent white color. Each piece of snow moves along a sinusoidal curve. Our model is quite simple, but the result looks pretty good for cartoon snow animation.

The size, position, and speed are random values in ranges defined in a setting class:

function SnowSettings(radiusRange, maxX, maxY, speedYRange,
                        speedX, alphaRange, moveXRange) {
    this.radiusRange = radiusRange;
    this.maxX = maxX;
    this.maxY = maxY;
    this.speedYRange = speedYRange;
    this.speedX = speedX;
    this.alphaRange = alphaRange;
    this.moveXRange = moveXRange;
}

We use the following values to config snow settings while initializing:

function initSnow(width, height) {
    var radiusRange = new Range(3, 10),
        speedYRange = new Range(1, 3),
        speedX = 0.05,
        alphaRange = new Range(0.5, 1.0),
        moveXRange = new Range(4, 18);
        
    var snowSettings = new SnowSettings(radiusRange,
                    width, 
                    height, 
                    speedYRange, 
                    speedX, 
                    alphaRange, 
                    moveXRange);
    
    var snow = [];
    var snowNumber = 200;
    for(var i = 0; i < snowNumber; ++i) {
        snow[i] = new Snow(snowSettings);
    }
    
    return snow;
}

function Range(min, max) {
    this.min = min;
    this.max = max;
}

Besides pieces of snow, there is also a background image, therefore we also need a class containing both snow and background image. The container class is defined below:

function ChristmasSnow(canvasId, imagePath) {
    var snowElement = document.getElementById(canvasId);
    this.canvasContext = snowElement.getContext("2d");
    
    this.width = snowElement.clientWidth;
    this.heigth = snowElement.clientHeight;
    
    this.image = initImage(imagePath);
    this.snow = initSnow(this.width, this.heigth);
}

function initImage(imagePath) {
    var image = new Image();
    image.src = imagePath;
    return image;
}

ChristmasSnow.prototype.render = function() {
    this.canvasContext.drawImage(this.image, 0, 0);
    
    for(var i = 0; i < this.snow.length; ++i) {
        this.snow[i].render(this.canvasContext);
    }
}

ChristmasSnow.prototype.update = function() {
    for(var i = 0; i < this.snow.length; ++i) {
        this.snow[i].update();
    }
}
    
With the two class above, we could define an API to create snow with a background image into a HTML5 canvas, as listed below:

SnowLib = {
    makeSnow: makeSnow
};

function makeSnow(canvasId, imagePath) {
    var christmasSnow = new ChristmasSnow(canvasId, imagePath);
    var renderAndUpdateFunc = renderAndUpdate(christmasSnow)
    setInterval(renderAndUpdateFunc, 15);
}

function renderAndUpdate(christmasSnow) {
    return function() {
        christmasSnow.render();
        christmasSnow.update();
    }
}

Users can create their own snow animation with the API SnowLib.makeSnow.