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.~