/* Adapted from http://jsfiddle.net/dtrooper/AceJJ/ TODO: * Try to get rid of ghosting * See if anything can be made more efficient * Make the canvas fit in the z-order */ (function( $ ) { var MAX_ROCKETS = 5, MAX_PARTICLES = 500; var FUNCTIONS = { 'init': function(element) { var jqe = $(element); // Check this element isn't already inited if (jqe.data('fireworks_data') !== undefined) { console.log('Looks like this element is already inited!'); return; } // Setup fireworks on this element var canvas = document.createElement('canvas'), canvas_buffer = document.createElement('canvas'), data = { 'element': element, 'canvas': canvas, 'context': canvas.getContext('2d'), 'canvas_buffer': canvas_buffer, 'context_buffer': canvas_buffer.getContext('2d'), 'particles': [], 'rockets': [] }; // Add & position the canvas if (jqe.css('position') === 'static') { element.style.position = 'relative'; } element.appendChild(canvas); canvas.style.position = 'absolute'; canvas.style.top = '0px'; canvas.style.bottom = '0px'; canvas.style.left = '0px'; canvas.style.right = '0px'; // Kickoff the loops data.interval = setInterval(loop.bind(this, data), 1000 / 50); // Save the data for later jqe.data('fireworks_data', data); }, 'destroy': function(element) { var jqe = $(element); // Check this element isn't already inited if (jqe.data('fireworks_data') === undefined) { console.log('Looks like this element is not yet inited!'); return; } var data = jqe.data('fireworks_data'); jqe.removeData('fireworks_data'); $(data.canvas).fadeOut(1500, function(){ // Stop the interval clearInterval(data.interval); // Remove the canvas data.canvas.remove(); }); } }; $.fn.fireworks = function(action) { // Assume no action means we want to init if (!action) { action = 'init'; } // Process each element this.each(function() { FUNCTIONS[action](this); }); // Chaining ftw :) return this; }; function launch(data) { if (data.rockets.length < MAX_ROCKETS) { var rocket = new Rocket(data); data.rockets.push(rocket); } } function loop(data) { // Launch a new rocket launch(data); // Update screen size if (data.canvas_width != data.element.offsetWidth) { data.canvas_width = data.canvas.width = data.canvas_buffer.width = data.element.offsetWidth; } if (data.canvas_height != data.element.offsetHeight) { data.canvas_height = data.canvas.height = data.canvas_buffer.height = data.element.offsetHeight; } // Fade the background out slowly data.context_buffer.clearRect(0, 0, data.canvas.width, data.canvas.height); data.context_buffer.globalAlpha = 0.9; data.context_buffer.drawImage(data.canvas, 0, 0); data.context.clearRect(0, 0, data.canvas.width, data.canvas.height); data.context.drawImage(data.canvas_buffer, 0, 0); // Update the rockets var existingRockets = []; data.rockets.forEach(function(rocket) { // update and render rocket.update(); rocket.render(data.context); // random chance of 1% if rockets is above the middle var randomChance = rocket.pos.y < (data.canvas.height * 2 / 3) ? (Math.random() * 100 <= 1) : false; /* Explosion rules - 80% of screen - going down - close to the mouse - 1% chance of random explosion */ if (rocket.pos.y < data.canvas.height / 5 || rocket.vel.y >= 0 || randomChance) { rocket.explode(data); } else { existingRockets.push(rocket); } }); data.rockets = existingRockets; // Update the particles var existingParticles = []; data.particles.forEach(function(particle) { particle.update(); // render and save particles that can be rendered if (particle.exists()) { particle.render(data.context); existingParticles.push(particle); } }); data.particles = existingParticles; while (data.particles.length > MAX_PARTICLES) { data.particles.shift(); } } function Particle(pos) { this.pos = { x: pos ? pos.x : 0, y: pos ? pos.y : 0 }; this.vel = { x: 0, y: 0 }; this.shrink = .97; this.size = 2; this.resistance = 1; this.gravity = 0; this.flick = false; this.alpha = 1; this.fade = 0; this.color = 0; } Particle.prototype.update = function() { // apply resistance this.vel.x *= this.resistance; this.vel.y *= this.resistance; // gravity down this.vel.y += this.gravity; // update position based on speed this.pos.x += this.vel.x; this.pos.y += this.vel.y; // shrink this.size *= this.shrink; // fade out this.alpha -= this.fade; }; Particle.prototype.render = function(c) { if (!this.exists()) { return; } c.save(); c.globalCompositeOperation = 'lighter'; var x = this.pos.x, y = this.pos.y, r = this.size / 2; var gradient = c.createRadialGradient(x, y, 0.1, x, y, r); gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")"); gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")"); gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)"); c.fillStyle = gradient; c.beginPath(); c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true); c.closePath(); c.fill(); c.restore(); }; Particle.prototype.exists = function() { return this.alpha >= 0.1 && this.size >= 1; }; function Rocket(data) { Particle.apply( this, [{ x: Math.random() * data.canvas.width * 2 / 3 + data.canvas.width / 6, y: data.canvas.height }] ); this.explosionColor = Math.floor(Math.random() * 360 / 10) * 10; this.vel.y = Math.random() * -3 - 4; this.vel.x = Math.random() * 6 - 3; this.size = 2; this.shrink = 0.999; this.gravity = 0.01; } Rocket.prototype = new Particle(); Rocket.prototype.constructor = Rocket; Rocket.prototype.explode = function(data) { var count = Math.random() * 10 + 80; for (var i = 0; i < count; i++) { var particle = new Particle(this.pos); var angle = Math.random() * Math.PI * 2; // emulate 3D effect by using cosine and put more particles in the middle var speed = Math.cos(Math.random() * Math.PI / 2) * 15; particle.vel.x = Math.cos(angle) * speed; particle.vel.y = Math.sin(angle) * speed; particle.size = 10; particle.gravity = 0.2; particle.resistance = 0.92; particle.shrink = Math.random() * 0.05 + 0.93; particle.flick = true; particle.color = this.explosionColor; data.particles.push(particle); } }; Rocket.prototype.render = function(c) { if (!this.exists()) { return; } c.save(); c.globalCompositeOperation = 'lighter'; var x = this.pos.x, y = this.pos.y, r = this.size / 2; var gradient = c.createRadialGradient(x, y, 0.1, x, y, r); gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")"); gradient.addColorStop(0.2, "rgba(255, 180, 0, " + this.alpha + ")"); c.fillStyle = gradient; c.beginPath(); c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true); c.closePath(); c.fill(); c.restore(); }; }(jQuery));