Rain Animation Effect with HTML5 canvas, CSS3, and JavaScript

[LIVE SAMPLE]

[DOWNLOAD SOURCE CODE]

Hi you! 🙂

I was given the task of writing a rain effect for a webpage. Of course, I started browsing the web for a ready-to-use piece of code. The best I could find was using white rectangles as rain drops, and the loop was managed by setInterval() function. So I updated the code with my preferences, and I’m going to share it with you here.

Let’s go through app.js to see what’s going on.


var context = null;
var particleArray = [];
var particleTimer = null;
var maxParticleCount = 300;
var animTimerId;

$(document).ready(function () { initRainCanvas(200); });

function initRainCanvas(interval) {
    context = $(".rainCanvas").get(0).getContext("2d");
    particleTimer = setInterval(addParticle, interval);
    animTimerId = window.requestAnimationFrame(animate);
    flash();
}

function animate() {
    update();
    paint();
    animTimerId = window.requestAnimationFrame(animate);
}

This code initializes the creation of raindrops, the random flashing mechanism, and also a forever loop that maintains the rain effect. If you’re a game programmer, you already know that technically a game is an ultimate loop with 2 mechanisms: one to update the game’s status, the other to render the changes on the screen. Here we have the same thing: update() function shifts rain drops positions, and paint() draw them on the canvas. In game development terminology, when we’re handling a lot of objects (particles) as a visual effect, we call it particle system and usually it is separated into a component called particle engine, so don’t be surprised for variable names like particleArray.

The other thing that is definitely worth mentioning here, is using window.requestAnimationFrame() instead of a regular setInterval(). Why? Because not only by using this, the browser calls your function in the perfect time, synchronized with rendering frame rate, but also the function that is called by requestAnimationFrame() will be optimized for GPU by browsers. This makes your animations 200 times smoother! 😀

These 2 functions take care of creating rain drops randomly, using one of the 3 rain drop images. Weirdly, Particle() function acts like a class here!  When the number of raindrops reaches 300, it stops the creation:
UPDATE: At the time of writing this article, I was not aware of TypeScript existence! By using TypeScript I can exchange the Particle function with a class definition, to have a more object oriented structure.

function addParticle() {
    particleArray[particleArray.length] = new Particle();
    if (particleArray.length == maxParticleCount)
        clearInterval(particleTimer);
}

function Particle() {
    this.x = Math.round(Math.random() * context.canvas.width);
    this.y = -10;
    this.drift = 4;
    this.speed = Math.round(Math.random() / 5) + 15;

    var rand = Math.random();
    this.rainDrop = rand < 0.33 ? $("#raindrop1").get(0) : rand < 0.66 ? $("#raindrop2").get(0) : $("#raindrop3").get(0);
}

update() function takes care of updating the position of raindrops. This algorithm move all raindrops downward and a little bit to right. shifting to right is controlled by a parameter called drift. Some particle systems destroy particles after a while, but in this code when rain drops reaches the bottom of the screen, we simply put them on top again.

function update() {
    for (var i = 0; i < particleArray.length; i++) {
        if (particleArray[i].y < context.canvas.height) {
            particleArray[i].y += particleArray[i].speed;
            if (particleArray[i].y > context.canvas.height)
                particleArray[i].y = -1;

            particleArray[i].x += particleArray[i].drift;
            if (particleArray[i].x > context.canvas.width)
                particleArray[i].x = 0;
        }
    }
}

 

finally, the paint() function clears the canvas first, and then draws all particles on their position, using drawImage() function.

function paint() {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    for (var i = 0; i < particleArray.length; i++) {
        context.drawImage(particleArray[i].rainDrop, particleArray[i].x, particleArray[i].y);
    }
}

The thunderbolt flashing effect is done by applying a keyframe-based animation on a container div on top of the canvas. At first I had added this to the canvas element itself, but for some reasons, it was not supporting transparency, so I have added a placeholder div on top of the canvas for this purpose. the JavaScript code adds .flashing class to flashDiv element in a random timeout between 4 and 12 seconds, and then removes this class after 2 seconds. flash() function calls itself, so the flashing effect displays forever.

function flash() {
    var randomTimeout = (Math.random() * 8 + 4) * 1000; // flashes in a random time between 4 and 12 seconds
    setTimeout(function () {
        setFlashClass();
        flash();
    }, randomTimeout);
}

function setFlashClass() {
    $(".flashDiv").addClass("flashing");
    window.setTimeout(function () { $(".flashDiv").removeClass("flashing") }, 2000);
}

The CSS code simply applies flashAnim keyframes to the flash div. As you can see this animation sets and resets the background color from a semi-transparent yellowish color to absolutely transparent, several times over the duration of 1 second.

.flashing {
    
    -webkit-animation: flashAnim ease-out 1s;
    -moz-animation: flashAnim ease-out 1s;
    -o-animation: flashAnim ease-out 1s;
    animation: flashAnim ease-out 1s;
}

@keyframes flashAnim {
    0% { background: transparent; }
    55% { background: rgba(254, 255, 202, 0.27); }
    60% { background: transparent; }
    75% { background: rgba(254, 255, 202, 0.27); }
    80% { background: transparent; }
    95% { background: rgba(254, 255, 202, 0.27); }
    100% { background: transparent; }
}

 

 

That’s it! please leave your comments and feedback down here. Adding a thunderbolt sound effect to this code and syncing it with the flashing animation, can extremely improve the user experience.

Background image is downloaded from here.

[LIVE SAMPLE]

[DOWNLOAD SOURCE CODE]

2 Comments

  1. Erick May 18, 2016 11:57 pm  Reply

    How can I make it just start at a fixed speed and not increasing?

    • maroonedsia June 6, 2016 2:38 pm  Reply

      Hi Erick,

      I’m really sorry that I’m getting back to you very late. I saw your comment while ago, then totally forgot to reply.

      Actually, the speed of rain fall is not increasing in this algorithm, but it is the number of rain drops that increases over the time.
      To have all the rain drops pre-added to the scene, you need to perform 3 steps:

      1. in app.js file, comment out line 18 and replace it with a simple function call:
      //particleTimer = setInterval(addParticle, interval);
      addParticle();

      2. Update addParticle() function to create rain drop objects all at once, instead of creating them by an interval timer. Here is the updated addParticle() function:
      function addParticle() {
      while (particleArray.length < = maxParticleCount) { particleArray[particleArray.length] = new Particle(); } }
      3. If you browse to the .html file now, you’ll see that all rain drops are lined up with the same y (vertical) location. It’s because the Particle() function that takes care about creating rain drop objects, is setting the rain drops location at -10 initially, which makes sense in previous algorithm, but to add all drops to the scene at once, you want them to have random vertical locations as well, so, remove the line that reads as “this.y = -10;” and replace it with this:
      this.y = Math.round(Math.random() * context.canvas.height);

      That’s it!
      You can download the updated files here: https://onedrive.live.com/redir?resid=2681762D21DAF94D!49018&authkey=!AJe3pDhBQGI8WTQ&ithint=file%2czip

      Cheers,
      Sia

Leave a comment please!