Create Stunning Rain & Snow Effects with a Canvas Drop Component

This article walks through building a reusable canvas‑based drop component that renders realistic rain and snow animations, explains the underlying JavaScript architecture, and provides complete source code with usage examples and future enhancement ideas.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Create Stunning Rain & Snow Effects with a Canvas Drop Component

Article Origin

The author needed rain and snow animation effects for a recent project, so a reusable canvasDrop component was created to demonstrate common falling‑object animations on an HTML5 canvas.

Demo and Usage

Include a canvas element and the script, then initialize the component:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>#canvas{width:100%;height:100%;}</style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script src="canvasDrop.js"></script>
    <script>
        canvasDrop.init({
            type: "rain",
            speed: [0.4,2.5],
            size_range: [0.5,1.5],
            hasBounce: true,
            wind_direction: -105,
            hasGravity: true
        });
    </script>
</body>
</html>

Two screenshots show the rain and snow results.

Using canvas for many moving objects is simpler and more performant than creating DOM elements.

Source Code Explanation

First, several global variables are defined, including wind direction, gravity, and a requestAnimFrame polyfill.

Core Objects

Vector : stores horizontal ( x ) and vertical ( y ) speed values and provides add and copy methods.

The Drop object represents a raindrop or snowflake. Its constructor randomly sets the initial position, radius, speed, and velocity based on the configured wind angle.

var Drop = function() {
    var randomEdge = Math.random()*2;
    if(randomEdge > 1){
        this.pos = new Vector(50 + Math.random() * canvas.width, -80);
    }else{
        this.pos = new Vector(canvas.width, Math.random() * canvas.height);
    }
    this.radius = (OPTS.size_range[0] + Math.random() * OPTS.size_range[1]) * DPR;
    this.speed = (OPTS.speed[0] + Math.random() * OPTS.speed[1]) * DPR;
    this.prev = this.pos;
    var eachAnger = 0.017453293;
    wind_anger = OPTS.wind_direction * eachAnger;
    speed_x = this.speed * Math.cos(wind_anger);
    speed_y = -this.speed * Math.sin(wind_anger);
    this.vel = new Vector(wind_x, wind_y);
};

The update method applies gravity (if enabled) and moves the drop, while the draw method renders a line for rain (using a Bézier curve) or a circle for snow.

Drop.prototype.update = function(){
    this.prev = this.pos.copy();
    if(OPTS.hasGravity){ this.vel.y += gravity; }
    this.pos.add(this.vel);
};

Drop.prototype.draw = function(){
    ctx.beginPath();
    if(OPTS.type == "rain"){
        ctx.moveTo(this.prev.x, this.prev.y);
        var ax = Math.abs(this.radius * Math.cos(wind_anger));
        var ay = Math.abs(this.radius * Math.sin(wind_anger));
        ctx.bezierCurveTo(this.pos.x+ax, this.pos.y+ay, this.prev.x+ax, this.prev.y+ay, this.pos.x, this.pos.y);
        ctx.stroke();
    }else{
        ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI*2);
        ctx.fill();
    }
};
Bounce : simple bounce objects created when a drop hits the ground; they have their own update and draw methods.

External Interface

update

Runs the animation loop: clears the canvas, updates and draws all drops and bounces, creates new drops when needed, and schedules the next frame with requestAnimFrame.

function update(){
    var d = new Date;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    var i = drops.length;
    while(i--){
        var drop = drops[i];
        drop.update();
        if(drop.pos.y >= canvas.height){
            if(OPTS.hasBounce){
                var n = Math.round(4 + Math.random()*4);
                while(n--) bounces.push(new Bounce(drop.pos.x, canvas.height));
            }
            drops.splice(i,1);
        }
        drop.draw();
    }
    if(OPTS.hasBounce){
        var i = bounces.length;
        while(i--){
            var bounce = bounces[i];
            bounce.update();
            bounce.draw();
            if(bounce.pos.y > canvas.height) bounces.splice(i,1);
        }
    }
    if(drops.length < OPTS.maxNum && Math.random() < drop_chance){
        for(var j=0, len=OPTS.numLevel; j<len; j++) drops.push(new Drop());
    }
    requestAnimFrame(update);
}

init

Initializes the canvas, handles high‑DPI screens, sets canvas dimensions, and calls setStyle to configure line width and colors based on the animation type.

function init(opts){
    OPTS = opts;
    canvas = document.getElementById(opts.id);
    ctx = canvas.getContext("2d");
    DPR = window.devicePixelRatio;
    canvasWidth = canvas.clientWidth * DPR;
    canvasHeight = canvas.clientHeight * DPR;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    drop_chance = 0.4;
    setStyle();
}

function setStyle(){
    if(OPTS.type == "rain"){
        ctx.lineWidth = 1 * DPR;
        ctx.strokeStyle = 'rgba(223,223,223,0.6)';
        ctx.fillStyle = 'rgba(223,223,223,0.6)';
    }else{
        ctx.lineWidth = 2 * DPR;
        ctx.strokeStyle = 'rgba(254,254,254,0.8)';
        ctx.fillStyle = 'rgba(254,254,254,0.8)';
    }
}

Conclusion

The simple drop component is now functional, but it has limitations: the public API is minimal, style customization is basic, and there is no support for pausing, speeding up, or slowing down the animation. Future work could expose more parameters, allow custom update / draw logic for drops and bounces, and add pause/acceleration controls.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationCanvasrain effectsnow effect
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.