1/* 2Adapted from http://jsfiddle.net/dtrooper/AceJJ/ 3 4TODO: 5 * Try to get rid of ghosting 6 * See if anything can be made more efficient 7 * Make the canvas fit in the z-order 8*/ 9 10(function( $ ) { 11 var MAX_ROCKETS = 3, 12 MAX_PARTICLES = 500; 13 14 var FUNCTIONS = { 15 'init': function(element) { 16 var jqe = $(element); 17 18 // Check this element isn't already inited 19 if (jqe.data('fireworks_data') !== undefined) { 20 console.log('Looks like this element is already inited!'); 21 return; 22 } 23 24 // Setup fireworks on this element 25 var canvas = document.createElement('canvas'), 26 canvas_buffer = document.createElement('canvas'), 27 data = { 28 'element': element, 29 'canvas': canvas, 30 'context': canvas.getContext('2d'), 31 'canvas_buffer': canvas_buffer, 32 'context_buffer': canvas_buffer.getContext('2d'), 33 'particles': [], 34 'rockets': [] 35 }; 36 37 // Add & position the canvas 38 if (jqe.css('position') === 'static') { 39 element.style.position = 'relative'; 40 } 41 element.appendChild(canvas); 42 canvas.style.position = 'absolute'; 43 canvas.style.top = '0px'; 44 canvas.style.bottom = '0px'; 45 canvas.style.left = '0px'; 46 canvas.style.right = '0px'; 47 48 // Kickoff the loops 49 data.interval = setInterval(loop.bind(this, data), 1000 / 50); 50 51 // Save the data for later 52 jqe.data('fireworks_data', data); 53 }, 54 'destroy': function(element) { 55 var jqe = $(element); 56 57 // Check this element isn't already inited 58 if (jqe.data('fireworks_data') === undefined) { 59 console.log('Looks like this element is not yet inited!'); 60 return; 61 } 62 var data = jqe.data('fireworks_data'); 63 jqe.removeData('fireworks_data'); 64 65 // Stop the interval 66 clearInterval(data.interval); 67 68 // Remove the canvas 69 data.canvas.remove(); 70 71 // Reset the elements positioning 72 data.element.style.position = ''; 73 } 74 }; 75 76 $.fn.fireworks = function(action) { 77 // Assume no action means we want to init 78 if (!action) { 79 action = 'init'; 80 } 81 82 // Process each element 83 this.each(function() { 84 FUNCTIONS[action](this); 85 }); 86 87 // Chaining ftw :) 88 return this; 89 }; 90 91 function launch(data) { 92 if (data.rockets.length < MAX_ROCKETS) { 93 var rocket = new Rocket(data); 94 data.rockets.push(rocket); 95 } 96 } 97 98 function loop(data) { 99 // Launch a new rocket 100 launch(data); 101 102 // Update screen size 103 if (data.canvas_width != data.element.offsetWidth) { 104 data.canvas_width = data.canvas.width = data.canvas_buffer.width = data.element.offsetWidth; 105 } 106 if (data.canvas_height != data.element.offsetHeight) { 107 data.canvas_height = data.canvas.height = data.canvas_buffer.he
107ight = data.element.offsetHeight; 108 } 109 110 // Fade the background out slowly 111 data.context_buffer.clearRect(0, 0, data.canvas.width, data.canvas.height); 112 data.context_buffer.globalAlpha = 0.9; 113 data.context_buffer.drawImage(data.canvas, 0, 0); 114 data.context.clearRect(0, 0, data.canvas.width, data.canvas.height); 115 data.context.drawImage(data.canvas_buffer, 0, 0); 116 117 // Update the rockets 118 var existingRockets = []; 119 data.rockets.forEach(function(rocket) { 120 // update and render 121 rocket.update(); 122 rocket.render(data.context); 123 124 // random chance of 1% if rockets is above the middle 125 var randomChance = rocket.pos.y < (data.canvas.height * 2 / 3) ? (Math.random() * 100 <= 1) : false; 126 127 /* Explosion rules 128 - 80% of screen 129 - going down 130 - close to the mouse 131 - 1% chance of random explosion 132 */ 133 if (rocket.pos.y < data.canvas.height / 5 || rocket.vel.y >= 0 || randomChance) { 134 rocket.explode(data); 135 } else { 136 existingRockets.push(rocket); 137 } 138 }); 139 data.rockets = existingRockets; 140 141 // Update the particles 142 var existingParticles = []; 143 data.particles.forEach(function(particle) { 144 particle.update(); 145 146 // render and save particles that can be rendered 147 if (particle.exists()) { 148 particle.render(data.context); 149 existingParticles.push(particle); 150 } 151 }); 152 data.particles = existingParticles; 153 154 while (data.particles.length > MAX_PARTICLES) { 155 data.particles.shift(); 156 } 157 } 158 159 function Particle(pos) { 160 this.pos = { 161 x: pos ? pos.x : 0, 162 y: pos ? pos.y : 0 163 }; 164 this.vel = { 165 x: 0, 166 y: 0 167 }; 168 this.shrink = .97; 169 this.size = 2; 170 171 this.resistance = 1; 172 this.gravity = 0; 173 174 this.flick = false; 175 176 this.alpha = 1; 177 this.fade = 0; 178 this.color = 0; 179 } 180 181 Particle.prototype.update = function() { 182 // apply resistance 183 this.vel.x *= this.resistance; 184 this.vel.y *= this.resistance; 185 186 // gravity down 187 this.vel.y += this.gravity; 188 189 // update position based on speed 190 this.pos.x += this.vel.x; 191 this.pos.y += this.vel.y; 192 193 // shrink 194 this.size *= this.shrink; 195 196 // fade out 197 this.alpha -= this.fade; 198 }; 199 200 Particle.prototype.render = function(c) { 201 if (!this.exists()) { 202 return; 203 } 204 205 c.save(); 206 207 c.globalCompositeOperation = 'lighter'; 208 209 var x = this.pos.x, 210 y = this.pos.y, 211 r = this.size / 2; 212 213 var gradient = c.createRadialGradient(x, y, 0.1, x, y, r); 214 gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")"); 215 gradient.addColorStop(0.5, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")"); 216 gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, 0.1)"); 217 218 c.fillStyle = gradient; 219 220 c.beginPath(); 221 c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true); 222 c.closePath(); 223 c.fill(); 224 225 c.restore(); 226 }; 227 228 Particle.prototype.exists = function() { 229 return this.alpha >= 0.1 && this.size >= 1; 230 }; 231 232 function Rocket(data) { 233 Particle.apply( 234 this, 235 [{ 236 x: Math.random() * data.canvas.width * 2 / 3 + data.canvas.width / 6, 237 y: data.canvas.height 238 }] 239 ); 240 241 this.explosionColor = Math.floor(Math.random() * 360 / 10) * 10; 242 this.vel.y = Math.random() * -3 - 4; 243 this.vel.x = Math.random() * 6 - 3; 244 this.size = 2; 245 this.shrink = 0.999; 246 this.gravity = 0.01; 247 } 248 249 Rocket.prototype = new Particle(); 250 Rocket.prototype.constructor = Rocket; 251 252 Rocket.prototype.explode = function(data) { 253 var count = Math.random() * 10 + 80; 254 255 for (var i = 0; i < count; i++) { 256 var particle = new Particle(this.pos); 257 var angle = Math.random() * Math.PI * 2; 258 259 // emulate 3D effect by using cosine and put more particles in the middle 260 var speed = Math.cos(Math.random() * Math.PI / 2) * 15; 261 262 particle.vel.x = Math.cos(angle) * speed; 263 particle.vel.y = Math.sin(angle) * speed; 264 265 particle.size = 10; 266 267 particle.gravity = 0.2; 268 particle.resistance = 0.92; 269 particle.shrink = Math.random() * 0.05 + 0.93; 270 271 particle.flick = true; 272 particle.color = this.explosionColor; 273 274 data.particles.push(particle); 275 } 276 }; 277 278 Rocket.prototype.render = function(c) { 279 if (!this.exists()) { 280 return; 281 } 282 283 c.save(); 284 285 c.globalCompositeOperation = 'lighter'; 286 287 var x = this.pos.x, 288 y = this.pos.y, 289 r = this.size / 2; 290 291 var gradient = c.createRadialGradient(x, y, 0.1, x, y, r); 292 gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")"); 293 gradient.addColorStop(0.2, "rgba(255, 180, 0, " + this.alpha + ")"); 294 295 c.fillStyle = gradient; 296 297 c.beginPath(); 298 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); 299 c.closePath(); 300 c.fill(); 301
302 c.restore(); 303 }; 304}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.