css代码:

*,body,html{
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
background: black;
}
*{
    box-sizing: border-box;
}
body{
    margin: 0;
    padding: 0;
}
canvas{
    position: absolute;
    z-index: 2;
    width: 100vw;
    height: 100vh;
}

svg{
    position: absolute;
    z-index: 1;
    width: 100vw;
    height: 100vh;
    background: linear-gradient(45deg, rgb(30,30,40), rgb(50,50,60) );
}


.cloud{
    transform: translate(0,0);
    animation-name: cloudSlide;
    animation-duration: 5s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-delay: 0s;
    animation-fill-mode: both;
    animation-timing-function: linear;    
}

@keyframes cloudSlide{
    from{
transform: translate(0px, 0px)
    }
    to{
transform: translate(30px, 0px);
    }
}

html代码:

<canvas id="rain-canvas"></canvas>

js代码:

const {innerWidth, innerHeight} = window;
const {random, abs, PI, floor} = Math;
let ctx, particles, particleNum, particleR, particleSpeed, particleLife;
function rand(min, max = 0, neg = false){
    if(!max) max = min, min = 0
    if(neg) return ( (random() - 0.5) * (max-min) + min )
    else return ( random() * (max-min) + min )
}

function Particle(x,y,r,dx,dy,l){
    this.x = x;
    this.y = y;
    this.r = r;
    this.dx = dx;
    this.dy = dy;
    this.l = l;
    this.init = function(lastPoint){
        this.r = rand(particleR.min, particleR.max);
        this.x = rand(0 + this.r, ctx.canvas.width - this.r);
        this.y = rand(10, 300);
        lastPoint.x = this.x;
        lastPoint.y = this.y;
        this.dx = 0;
        this.dy = rand(particleSpeed.min, particleSpeed.max);
        this.l = rand(particleLife.min, particleLife.max);
        this.draw(lastPoint);
    }
    this.draw = function(lastPoint){
        ctx.beginPath();
        ctx.strokeStyle = "rgba(80,80,"+rand(80,130)+","+this.l+")";
        ctx.lineWidth = this.r;
        ctx.moveTo(lastPoint.x, lastPoint.y);
        ctx.lineTo(this.x + this.dx, this.y + this.dy);
        ctx.stroke();
        ctx.closePath();
    }
    this.checkBounds = function(lastPoint){
        if(this.y > ctx.canvas.height || this.l <= 0){
            this.init(lastPoint)
        }else this.draw(lastPoint)
    }
    this.update = function(){
        let lastPoint = { x: this.x, y: this.y};
        this.x += this.dx;
        this.y += this.dy;
        // this.l -= 1 * (1/60);
        this.checkBounds(lastPoint);
    }
}




window.onload = function(){
    let clouds = document.querySelectorAll('g');
    let duration = 15 * clouds.length + 10;
    clouds.forEach(cloud => {
        duration -= 10;
        cloud.style.animationDuration = duration + 's';       
    });

    let canvas = document.getElementById('rain-canvas');
    ctx = canvas.getContext("2d");
    resize();
}




function initCanvas(){

    initParticles();
    animate();
}


function initParticles(){
    particles = [];
    particleNum = 600;
    particleR = {min: 1, max: 2};
    particleSpeed = {min: 5, max: 10};
    particleLife = {min: 0.1, max: 1};

    for(let i = 0; i < particleNum; i++){
        let r = rand(particleR.min, particleR.max);
        let x = rand(0 + r, ctx.canvas.width - r);
        let y = rand(0, ctx.canvas.height);
        let dx = 0;
        let dy = rand(particleSpeed.min, particleSpeed.max);
        let l = rand(particleLife.min, particleLife.max);
        particles.push( new Particle(x,y,r,dx,dy,l) )
    }
}

function resize(){
    ctx.canvas.width = innerWidth;
    ctx.canvas.height = innerHeight;
    initCanvas()
}
function animate(){
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
    // ctx.fillStyle = "rgba(255,255,255,0.01)";
    // ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height);
    particles.forEach(particle => {
        particle.update();
    });
    requestAnimationFrame(animate);
}

addEventListener('resize', resize);