Here we are going to animate fireworks, with JavaScript and HTML5. The animation result looks like the image blow:
If you are interested to the dynamic animation, please go the webpage http://jsfiddle.net/zhedahht/rKsyE/. Additionally, the source code is shared at https://github.com/zhedahht/JsInPractice/tree/master/Fireworks.
Now let's dive into the source code of the fireworks. Fireworks are defined as lots of moving particles. Particles are created with the following JavaScript constructor:
If you are interested to the dynamic animation, please go the webpage http://jsfiddle.net/zhedahht/rKsyE/. Additionally, the source code is shared at https://github.com/zhedahht/JsInPractice/tree/master/Fireworks.
Now let's dive into the source code of the fireworks. Fireworks are defined as lots of moving particles. Particles are created with the following JavaScript constructor:
function Particle(pos, speed, resistance, gravity, size) { var curPos = { x: pos.x, y: pos.y }; var curSpeed = speed; this.render = function(context, color) { context.fillStyle = color; context.beginPath(); context.arc(curPos.x, curPos.y, size, 0, Math.PI * 2, true); context.closePath(); context.fill(); } this.update = function() { curSpeed.x = curSpeed.x * resistance + gravity.x; curSpeed.y = curSpeed.y * resistance + gravity.y; curPos.x += curSpeed.x; curPos.y += curSpeed.y; } }
Each particle is response to render itself as a dot on a canvas, and to update its position.
A group of particles are shot from time to time, which share some common properties such as color and the initial position before explosion. A group of particles in a single shot are defined as:
function ParticleGroup(pos, canvasSize, numberOfParticles) { var shotHeight = randomInRange(canvasSize.height * 0.50, canvasSize.height * 0.75); var life = 100; var age = 0; var particles = initParticles(pos, canvasSize); var color = pickColor(); this.render = function(context) { var strColor = color.toString(); particles.forEach(function(particle) { particle.render(context, strColor); }); } this.update = function() { age++; updateColor(); particles.forEach(function(particle) { particle.update(); }); } this.isDead = function() { return age >= life; } function initParticles(pos, canvasSize) { var particles = []; var particlePos = { x: pos.x, y: pos.y - shotHeight } var resistance = 0.985; var gravity = { x: 0, y: 0.005 } var size = 2; var maxSpeed = randomInRange(2.4, 3.2); for(var i = 0; i < numberOfParticles; ++i) { var angle = randomInRange(0, Math.PI * 2); var linearSpeed = randomInRange(0, maxSpeed); var speed = { x: linearSpeed * Math.cos(angle), y: linearSpeed * Math.sin(angle), } var particle = new Particle(particlePos, speed, resistance, gravity, size); particles.push(particle); } return particles; } function updateColor() { var alpha = 1.0; var oldness = age / life; if (oldness > 0.90) { alpha = 10 * (1 - oldness); color.setAlpha(alpha); } } }
The following constructor is to create fireworks, which is response to shot new group of particles from time to time, and remove groups when they get too old:
function Firework(pos, canvasSize, numberOfParticles) { var shots = []; this.render = function(context) { shots.forEach(function(shot) { shot.render(context); }); } this.update = function() { removeDeadShots(); shots.forEach(function(shot) { shot.update(); }); } this.shot = function() { var newShot = new ParticleGroup(pos, canvasSize, numberOfParticles); shots.push(newShot); } function removeDeadShots() { for(var i = 0; i < shots.length; ++i) { shot = shots[i]; if (shot.isDead()) { shots.splice(i, 1); } } } }
Usually there are many fireworks shooting at the same time. The following constructor create a group of fireworks:
function FireworkGroup(canvasId, numberOfFireworks, numberOfParticles) { var fireworkGroupElement = document.getElementById(canvasId); var context = fireworkGroupElement.getContext("2d"); var width = fireworkGroupElement.clientWidth; var height = fireworkGroupElement.clientHeight; var fireworks = initFireworkGroup(width, height); this.getFireworks = function() { return fireworks;< }
this.render = function() { context.fillStyle = "#010212"; context.fillRect(0, 0, width, height) fireworks.forEach(function(firework) { firework.render(context); }); } this.update = function() { fireworks.forEach(function(firework) { firework.update(); }); } this.shot = function() { fireworks.forEach(function(firework) { firework.shot(); }); }
function initFireworkGroup(width, height) { var fireworks = []; for(var i = 0; i < numberOfFireworks; ++i) { var pos = { x: Math.round((width / numberOfFireworks) * (i + 0.5)), y: height * 0.95 }; var canvasSize = { width: width, height: height };
fireworks[i] = new Firework(pos, canvasSize, numberOfParticles); } return fireworks; } }
With the code above, we can create fireworks on a canvas when the document is loaded:
$(document).ready(function () { makeFireworkGroup("canvasForfireworks", 3, 300); });
function makeFireworkGroup(canvasId, numberOfFireworks, numberOfParticles) { function shotFireworkGroup(fireworkGroup) { var fireworks = fireworkGroup.getFireworks(); fireworks.forEach(function(firework) { shotFirework(firework); }); } function shotFirework(firework) { firework.shot(); var wait = randomInRange(1200, 1600); setTimeout(shotFirework, wait, firework); } function renderAndUpdate(fireworks) { return function() { fireworks.render(); fireworks.update(); }; }
var fireworks = new FireworkGroup(canvasId, numberOfFireworks, numberOfParticles); shotFireworkGroup(fireworks); var renderAndUpdateFunc = renderAndUpdate(fireworks) setInterval(renderAndUpdateFunc, 15); }
In the code above, 300 particles are shot in about every 1.5 seconds, and the canvas will be updated in every 15 milliseconds.
If you find that there are more and more particles on your canvas, it means there are too many particles. You may decrease the number of particles in each shooting.
Finally, some utility functions are needed for randomness and particle colors, as listed below:
function randomInRange(min, max) { return Math.random() * (max - min) + min; }
var pickColor = (function() { var colors = [ new Color(0x00, 0xFF, 0xFF), // Aqua new Color(0x8A, 0x2B, 0xE2), // BlueViolet new Color(0xDC, 0x14, 0x3C), // Crimson new Color(0xFF, 0x14, 0x93), // DeepPink new Color(0x22, 0x8B, 0x22), // ForestGreen new Color(0xAD, 0xFF, 0x2F), // GreenYello new Color(0xFF, 0x69, 0xB4), // HotPink new Color(0xCD, 0x5C, 0x5C), // IndianRed new Color(0xF0, 0xE6, 0x8C), // Khaki new Color(0x7C, 0xFC, 0x00), // LawGreen new Color(0x00, 0xFA, 0x9A), // MediumSrpingGreen new Color(0xFF, 0xA5, 0x00), // Orange new Color(0x80, 0x00, 0x00), // Purple new Color(0xFF, 0x00, 0x00), // Red new Color(0x87, 0xCE, 0xEB), // SkyBlue new Color(0xFF, 0x63, 0x47), // Tomato new Color(0xEE, 0x82, 0xEE), // Violet new Color(0xF5, 0xDE, 0xB3), // Wheat new Color(0xFF, 0xFF, 0x00) // Yellow ]; return function() { var index = Math.round(randomInRange(0, colors.length - 1)); return colors[index].clone(); } })();
function Color(red, green, blue, alpha) { var r = red, g = green, b = blue, a = alpha; this.toString = function() { if (a === undefined) { return "rgb(" + r + "," + g + "," + b + ")"; } return "rgba(" + r + "," + g + "," + b + "," + a + ")"; } this.setAlpha = function(newAlpha) { a = newAlpha; } this.clone = function() { return new Color(r, g, b, a); } }
No comments :
Post a Comment