Creating a Realistic Rain Effect with Canvas and JavaScript

by Brian Rinaldi on September 23, 2013

The modern web is always changing, and this article is more than two years old.

By Marek Brodziak

I recently released a fun project called rainyday.js. I consider it a rather humble piece of code and, in fact, it is more or less my first contact with JavaScript on a level that requires a little more than displaying alert popups. Hopefully, some of you will find it useful and interesting.

The idea behind rainyday.js is to create a light JavaScript library that makes use of the HTML5 canvas to render an animation of raindrops falling on a glass surface. Simple enough, yet challenging at times, especially since we’re trying to avoid that cartoonish look usually attributed to JavaScript animations and at the same time make sure the animations work smoothly.

In this article I’ll try to describe the overall approach as well as the “little things”, so bare with me. But first, you might want to check out the following demos I’ve prepared, to give you an idea of what I’m actually talking about (click the images to open the demo on JSBin):

rainydaydemo1

rainydaydemo2

Preparing Canvas Layers

The animation uses three different canvas objects in order to achieve the layering effect, as can be seen on the figure below.

rainyday_layers

The bottom layer contains the original image, scaled to the size requested via the API. The contents of the image are simply drawn on the canvas and afterwards the image is blurred so that it appears to be out of focus. The current implementation makes use of the Stack Blur Algorithm by Mario Klingemann.

The middle layer is basically an invisible canvas. It is not a part of the DOM tree, but instead, serves as a helper when rendering raindrop reflections. The original image is rotated and flipped accordingly, which makes the entire process easier later on.

Finally the top layer (the glass) is the one we use for drawing raindrops. It is positioned directly above the first canvas.

The entire concept is pretty simple and easy to understand. Having three canvas instances instead of one makes the script code much simpler in terms of readability and significantly improves performance.

A Closer look at the Raindrops

Rendering raindrops is one of the most important aspects of rainyday.js. While this is a relatively simple task for small drops, in case of the larger ones there are a number of factors that need to be considered. For example, we need to make sure the shape of the drops are properly randomized.

In order to achieve that, rainyday.js uses an algorithm to approximate the shape of a circle (see this cool article on imperfect circles by Dan Gries) with a couple of tweaks. As a result, the shapes rendered by the script look something like this:

rainyday_drops

The output of the algorithm is a linked list of points determined along the circle line. Drawing the raindrop is as simple as connecting them together while slightly randomizing the radius along the way, and clipping whatever we choose as the reflection to the final shape.

Running the animation

Finally, the last piece of the puzzle is the animation “engine”, which consists of three modules. Each one of them depends on the previous one and the output is what you can see in the demos at the top of this article. The modules are:

  1. The rain module – a JavaScript timing event scheduled whenever the rain() method is invoked. Using the time-interval specified by the user, this module places new drops at random positions on the canvas. The size of a new drop is determined by the presets, which allow users to control the amount and size of raindrops within the animation. Using an example from one of the demos, the following invocation of the rain() method will result in a new raindrop added to the canvas every 100ms. Size of those drops will be distributed as follows: 88% between 3 and 6 (minimum of 3 + a random value between 0 and 3), 2% (0.9 - 0.88) of size 5 and the remaining 10% (1 - 0.9) between 6 and 8:
    engine.rain([
        engine.preset(3, 3, 0.88),
        engine.preset(5, 5, 0.9),
        engine.preset(6, 2, 1),
    ], 100);
  2. The gravity module – once a raindrop is added to the animation, an additional timing event is scheduled to control the drop movement on the canvas by modifying its position on the Y axis (and on the X axis as well for that matter, in order to simulate rain falling from a given angle). At the moment rainyday.js delivers two different implementations of the gravity function: GRAVITY_LINEAR (simple gravity with constant acceleration) and GRAVITY_NON_LINEAR (a little more random movement of the drops). Selecting a gravity function is as simple as calling:
    engine.gravity = engine.GRAVITY_NON_LINEAR;
  3. The trail module – a function executed after each gravity movement in order to render a trail behind a falling drop. The TRAIL_DROPS function implements a trail of smaller drops, while TRAIL_NONE disables the trailing. Selecting a trailing function consists of calling:
    engine.trail = engine.TRAIL_DROPS;

Where to Go From Here

The source code of rainyday.js is available on GitHub, so if you’ve enjoyed it, go and have a look. For the next release (this article has been prepared based on 0.1.1) I’ll be working on implementing collision detection between raindrops as well as some other minor improvements I have in mind. Any comments, suggestions and feedback (of any sort really) are very welcome.

35 comments"

  1. Remy Sharp says:

    I’m the author of jsbin.com – and though I love what you’ve done, the 800K or so of dataurl that was put in the bin as an example caused some serious CPU issues on my server (due to query string parsing on every key stroke). I’ve had to make a change to your bins and am trying to get the CORS access via src.sencha.io’s service (which looking now isn’t always working, but then, neither is your link to rawgithub.com – so I guess it’s not perfect).

    It does raise the point that jsbin could provide an asset hosting service (which is on the list), but for now, I’ve had to make the change to keep jsbin running. Sorry if this is a problem.

    1. remotesynth says:

      Thanks Remy. I will chat with the author about making changes to the demos (particularly the use of rawgithub.com). If there are other changes that should be made that would help, please let me know.

    2. Marek Brodziak says:

      Hi Remy. Very sorry about this… Indeed I didn’t expect this much traffic and apparently rawgithub failed as well. I’ve since committed the demos to github instead so that people run them offline. This article will be updated shortly.

      Sorry again,
      Marek

      1. I recommend using S3 and then making sure the image file has open CORS headers.

  2. Pat says:

    Very impressive. The demos don’t work for me for some reason…

  3. Ravneet says:

    Demos do not run, get a cross origin image load denied error in Chrome console.

    1. Marek Brodziak says:

      I’ve migrated the demos to gh-pages:
      http://maroslaw.github.io/rainyday.js/demo1.html
      http://maroslaw.github.io/rainyday.js/demo2.html

      Feel free to use those 🙂

      1. kashyap says:

        thanks n xllent

  4. qwert says:

    Wow, these demos are really good.

    Can I set these as my Desktop wallpaper somehow? Please?

  5. Derryl says:

    Looks awesome!

    To really complete the effect, though, I think the raindrops must be able to:
    1) travel in irregular paths
    2) merge with one another when they collide

    1. Marek Brodziak says:

      Indeed, that’s what I’m working on right now!

      1. Derryl says:

        Just curious — are you using any sort of reference videos to help understand the behavior of droplets? (It never rains here in Los Angeles… so it’d be hard for me without some kind of source material :-p)

      2. Marek Brodziak says:

        It rains quite a bit here in Warsaw, so…. 🙂

  6. Big Al says:

    A very impressive visual effect. Thanks for sharing.

  7. Is there a way to apply this to an ordinary (HTML) web page w/o overlaying an image on top of it. I think it would be fun to set-up a timer so that if a user of my app doesn’t do anything for say, five minutes, this effect starts. Thanks!

  8. Tim says:

    Odd that on FF24 on 1 pc, it’s throwing a ‘SecurityError: The operation is insecure’ and not displaying drops, but works fine in FF24 on another pc

  9. Theo says:

    Fantastic effect, congratulations for the great work and thanks for sharing it!

  10. kozee says:

    Marek its rally SUPERB ! ! !. Expecting more nature based realistic works.. Congratulations.

  11. Slim says:

    very great article , i love it 🙂

  12. saad says:

    Awesome visual effect. Thanks for the hard work and thanks a bunch for sharing it. I wanted to request a small feature to rainyday.js next release. since you are able to control density of raindrops, would it be possible to add like a smeared blurr to the raindrop before it actually starts moving and then forms a clearer raindrop.
    I am more than willing to contribute some of my time to this project if you need help.

  13. Hey Marek,

    Just wanted to say thanks for creating this.
    It’s beautiful, so much so that we integrated it into our home page @ http://residr.com.

    The intensity of the rain is actually driven by Vancouver’s current weather conditions.

    Great work, thank you, and good luck with future developments.

  14. Awesome. Thanks for sharing!!!

  15. Downloaded from github your sources, but they do not work for some reason, but the demo you provided works fine.
    What’s the problem does not tell me?

  16. Shejan says:

    Hi,
    I download it from github but it is not working in my local server.Can you please show us how to use it?

  17. meme says:

    Awesomeee!!
    how to combine with wowslider?

Leave a Reply

Your email address will not be published. Required fields are marked *