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):
Preparing Canvas Layers
The animation uses three different canvas objects in order to achieve the layering effect.
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 Algorithmby 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 (seethis cool article on imperfect circles by Dan Gries) with a couple of tweaks.
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:
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:
3 and 6(minimum of 3 + a random value between 0 and 3),
2% (0.9 - 0.88)of size
5and 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);
- 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
Yaxis (and on the
Xaxis 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;
- The trail module – a function executed after each gravity movement in order to render a trail behind a falling drop. The
TRAIL_DROPSfunction implements a trail of smaller drops, while
TRAIL_NONEdisables the trailing. Selecting a trailing function consists of calling:
engine.trail = engine.TRAIL_DROPS;