Fireworks produced by canvas

Keywords: Javascript github Mobile

Recently, I feel that canvas is very interesting. In my spare time, I have studied it. I have consulted some ideas on the internet. I don't say much. I'm going to start.

github address: https://github.com/aWhiteBear/fireworks

Demonstration address: https://awhitebear.github.io/fireworks/

The effect of the picture is as follows: (FPS can be displayed in the upper right corner. It's time to see the performance of your computer.

 

First of all, let's talk about the train of thought. In fact, it's very simple. Fireworks are divided into two parts: 1. Skyscrapers. 2. Explosive effect. When the vertical velocity of the monkey is zero, let it explode.

Here are some constants that will be used in later classes

 1 const canvas = document.getElementById('canvasNode');
 2 const ctx = canvas.getContext('2d');
 3 canvas.width = window.innerWidth;
 4 canvas.height = window.innerHeight;
 5 const CANVAS_WIDTH = canvas.width;
 6 const CANVAS_HEIGHT = canvas.height;
 7 const GRAVITATIONAL = 2.5; // Simulated gravity acceleration
 8 const AIR_RESISTANCE = 1; // Simulated Air Resistance
 9 const EVERY_FIREWORK_TIME = 3; // The duration of each firework in seconds
10 const FRAME = 60;
class FlyingMonkey{ // Sky monkeys, launched into the air, can only be launched vertically at present
    constructor(x,y,speedX,speedY){
        this.x = x; // x,y It's the coordinates of the monkey's position.
        this.y = y;
        this.speedX = speedX;
        this.speedY = speedY;
        this.opacity = 1;
        this.count = 50; // The monkey and its tail are drawn from these 50 circles.
        for(let i=0;i<this.count;i++){
            this.createCircle(i);
        }
    }
    createCircle(i) { // Create the Circle Tail of Sky Monkey
        ctx.beginPath();
        ctx.save();
        ctx.globalCompositeOperation = 'lighter';
        ctx.fillStyle = `rgba(245,123,63,${this.opacity})`;
        ctx.arc(this.x + (Math.random()-0.5) * i/10 + i/this.count * this.speedX, this.y + i/this.count * this.speedY ,8 - (6 * i/this.count),0,2 * Math.PI);
        ctx.fill();
        ctx.restore();
        ctx.closePath();
    }
}

The top is the Skyscraper, the first fireworks to shoot up, and the bottom is the fireworks.

class Firework { // Fireworks, explosive
    constructor(x,y,speedX,speedY){
        this.x = x;
        this.y = y;
        this.speedX = speedX;
        this.speedY = speedY;
        this.opacity = 1;
        this.count = 500; // The explosive effect of fireworks consists of 500 points.
        this.AllFireworks = [];

        this.createAllFirework();
        Launch.arrFirework.push(this);
    }
    createAllFirework(){
        let r = Math.floor(Math.random()*256), g = Math.floor(Math.random()*256) , b =Math.floor(Math.random()*256);
        for(let i=0;i<this.count;i++){
            this.AllFireworks.push({
                r,g,b,
                x:this.x,
                y:this.y,
                opacity:1,
                speedX:this.speedX * i/this.count*10 *(Math.random()-0.5),
                speedY:this.speedY * i/this.count*10 *(Math.random()-0.5)
            });
        }
        this.updateAllFirework();
    }
    updateAllFirework(){
        for(let i=0;i<this.AllFireworks.length;i++){
            let {r,g,b,x,y,speedX,speedY,opacity} = this.AllFireworks[i];
            this.AllFireworks[i].y = y - speedY/FRAME;
            this.AllFireworks[i].x = x - speedX/FRAME;
            this.AllFireworks[i].opacity = opacity - 1/ FRAME / EVERY_FIREWORK_TIME;
            this.AllFireworks[i].speedY = speedY - GRAVITATIONAL;
            if(Math.abs(speedX)>3/FRAME) { // Speed <= 3/FRAME thinks it's stopped
                this.AllFireworks[i].speedX = speedX - (speedX>0?AIR_RESISTANCE:(AIR_RESISTANCE*(-1)));
            } else {
                this.AllFireworks[i].speedX = 0;
            }
            ctx.beginPath();
            ctx.save();
            ctx.globalCompositeOperation = 'lighter';
            ctx.fillStyle = `rgba(${r},${g},${b},${this.AllFireworks[i].opacity})`;
            ctx.arc(this.AllFireworks[i].x , this.AllFireworks[i].y  ,2,0,2 * Math.PI);
            ctx.fill();
            ctx.restore();
            ctx.closePath();
        }
    }
}

Here's the start class, which is used to launch Skyscrapers

class Start{
    constructor(x,y,speedX,speedY){
        Launch.arrFlyingMonkey.push(this);
        this.x = x;
        this.y = y;
        this.speedX = speedX;
        this.speedY = speedY;
        this.begin();
    }
    begin(){
        this.y = this.y - this.speedY/FRAME; // Speed decreases
        this.x = this.x - this.speedX/FRAME;
        this.speedY = this.speedY - GRAVITATIONAL;
        new FlyingMonkey(this.x, this.y, this.speedX, this.speedY);
    }
}

Here's the launch class, which renders the animation with the request Animation Frame

class Launch{ // launch
    constructor(){
        this.fps=0;
        this.sum=0;// Frame count counter 60 frames a cycle
        this.draw = this.draw.bind(this);
        this.draw();
    }
    draw(){
        ctx.clearRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);
        this.updateFps();
        Launch.arrFlyingMonkey.forEach((item,i)=>{
            item.begin();
            if(item.speedY < 0){
                Launch.arrFlyingMonkey.splice(i,1);
                new Firework(item.x,item.y,7*7,5*7); // Width-to-height ratio of fireworks:7:5
            }
        });
        Launch.arrFirework.forEach((item,i)=>{
            item.updateAllFirework();
        });
        if(Launch.arrFirework.length>5){ // Clear arrFirework,Avoid taking up too much memory, in fact, you can also pass through EVERY_FIREWORK_TIME and Launch.timer Clean up more timely. length > EVERY_FIREWORK_TIME/Launch.timer
            Launch.arrFirework.shift();
        }
        requestAnimationFrame(this.draw);
    }
    updateFps(){
        if(this.sum++>=60){
            this.sum = 0;
            let nowTime = new Date().getTime();
            this.fps = 60/(nowTime - Launch.lastTime) *1000;
            Launch.lastTime = nowTime;
        }
        ctx.save();
        ctx.fillStyle = 'red';
        ctx.font="20px Arial";
        ctx.fillText(`FPS: ${~~this.fps}`,CANVAS_WIDTH - 100,50);
        ctx.restore();
    }
}

Then add Launch static attributes

/** Add Launch static attributes*/
Launch.arrFlyingMonkey = [];
Launch.arrFirework = [];
Launch.timer = setInterval(()=>{
    new Start(CANVAS_WIDTH * (Math.random() * 0.8 + 0.1),CANVAS_HEIGHT * 0.9,0,300 *(Math.random()*0.5 + 1));
},1500);
Launch.lastTime = new Date().getTime();

Finally, in the new Launch(); can be launched out.

There are many other areas to optimize the code. In some mobile browsers, fps will be getting lower and lower. The picture will change into cards. If possible, we should optimize it later. We can also discuss how to optimize it better. We can guide you in the comments section. Thank you for your valuable advice.~

Posted by pcoder on Sun, 07 Apr 2019 07:06:31 -0700