diff --git a/css/style.css b/css/style.css index dab40e8..9ac9019 100644 --- a/css/style.css +++ b/css/style.css @@ -408,4 +408,16 @@ body{ } .dn{ display: none; +} +.w3-agilefireworks{ + top: 0; + left: 0px; + width: 100%; + height: 100%; + z-index: 999; +} +.agilefireworks-bg{ + height: 100vh; + width: 100%; + background: rgba(0,0,0,.2); } \ No newline at end of file diff --git a/dancing.html b/dancing.html index 3ebaf4d..1782691 100644 --- a/dancing.html +++ b/dancing.html @@ -13,7 +13,7 @@
-
+
@@ -140,9 +140,24 @@
+
+ + + + + + diff --git a/js/fireworks.js b/js/fireworks.js new file mode 100644 index 0000000..26e813f --- /dev/null +++ b/js/fireworks.js @@ -0,0 +1,304 @@ +/* +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 = 30, + 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 = 'fixed'; + } + 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'); + + // Stop the interval + clearInterval(data.interval); + + // Remove the canvas + data.canvas.remove(); + + // Reset the elements positioning + data.element.style.position = ''; + } + }; + + $.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));