Created
June 19, 2017 14:54
-
-
Save JeffreyHyer/9a9a684837129676738c8807fe4cf599 to your computer and use it in GitHub Desktop.
Launch fireworks in the browser using a canvas element to render the animated rockets/particles on the screen.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Adapted from the original source at: https://jsfiddle.net/dtrooper/AceJJ/ | |
* | |
* + Refactored to add support for ES6 classes, functions, and modules. | |
* - Removed support for using the mouse position/events to control the fireworks | |
* | |
* Draws a canvas over the entire width/height of the screen and launches | |
* fireworks until told to stop. | |
* | |
* Usage: | |
* import Fireworks from 'Fireworks'; | |
* | |
* var f = new Fireworks(); | |
* f.start(); | |
* | |
* // When you're ready for the firworks to stop | |
* // Probably best to bind this to the mouse and/or keyboard events | |
* // e.g. On click, [esc], [space], etc. | |
* f.stop(); | |
*/ | |
'use strict'; | |
class Particle { | |
constructor(pos) { | |
this.pos = { x: (pos ? pos.x : 0), y: (pos ? pos.y : 0) }; | |
this.vel = { x: 0, y: 0 }; | |
this.shrink = 0.97; | |
this.size = 2; | |
this.resistance = 1; | |
this.gravity = 0; | |
this.flick = false; | |
this.alpha = 1; | |
this.fade = 0; | |
this.color = 0; | |
} | |
update() { | |
this.vel.x *= this.resistance; | |
this.vel.y *= this.resistance; | |
this.vel.y += this.gravity; | |
this.pos.x += this.vel.x; | |
this.pos.y += this.vel.y; | |
this.size *= this.shrink; | |
this.alpha -= this.fade; | |
} | |
render(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(); | |
} | |
exists() { | |
return this.alpha >= 0.1 && this.size >= 1; | |
} | |
} | |
class Rocket extends Particle { | |
constructor(x, y) { | |
super({ x: x, y: y }); | |
this.explosionColor = 0; | |
} | |
explode(particles) { | |
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; | |
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; | |
particles.push(particle); | |
} | |
} | |
render(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(1, "rgba(0, 0, 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(); | |
} | |
} | |
class Fireworks { | |
constructor() { | |
this.SCREEN_WIDTH = window.innerWidth; | |
this.SCREEN_HEIGHT = window.innerHeight; | |
this.canvas = document.createElement('canvas'); | |
this.context = this.canvas.getContext('2d'); | |
this.particles = []; | |
this.rockets = []; | |
this.MAX_PARTICLES = 400; | |
this.colorCode = 0; | |
this.launchTimer = null; | |
this.loopTimer = null; | |
} | |
launch() { | |
if (this.rockets.length < 10) { | |
var x = Math.floor((Math.random() * this.SCREEN_WIDTH)); | |
var rocket = new Rocket(x, this.SCREEN_HEIGHT); | |
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10; | |
rocket.vel.y = Math.random() * -3 - 4; | |
rocket.vel.x = Math.random() * 6 - 3; | |
rocket.size = 8; | |
rocket.shrink = 0.999; | |
rocket.gravity = 0.01; | |
this.rockets.push(rocket); | |
} | |
} | |
loop() { | |
this.context.fillStyle = "rgba(0, 0, 0, 0.05)"; | |
this.context.fillRect(0, 0, this.SCREEN_WIDTH, this.SCREEN_HEIGHT); | |
var existingRockets = []; | |
for (var i = 0; i < this.rockets.length; i++) { | |
this.rockets[i].update(); | |
this.rockets[i].render(this.context); | |
var randomChance = ((this.rockets[i].pos.y < ((this.SCREEN_HEIGHT * 2) / 3)) ? (Math.random() * 100 <= 1) : false); | |
if ((this.rockets[i].pos.y < this.SCREEN_HEIGHT / 5) || | |
(this.rockets[i].vel.y >= 0) || | |
(randomChance)) | |
{ | |
this.rockets[i].explode(this.particles); | |
} else { | |
existingRockets.push(this.rockets[i]); | |
} | |
} | |
this.rockets = existingRockets; | |
var existingParticles = []; | |
for (var i = 0; i < this.particles.length; i++) { | |
this.particles[i].update(); | |
if (this.particles[i].exists()) { | |
this.particles[i].render(this.context); | |
existingParticles.push(this.particles[i]); | |
} | |
} | |
this.particles = existingParticles; | |
while (this.particles.length > this.MAX_PARTICLES) { | |
this.particles.shift(); | |
} | |
} | |
start() { | |
document.body.appendChild(this.canvas); | |
this.canvas.width = this.SCREEN_WIDTH; | |
this.canvas.height = this.SCREEN_HEIGHT; | |
this.canvas.setAttribute('style', 'position: fixed; top: 0; left: 0; z-index: 9999'); | |
if (this.launchTimer || this.loopTimer) { | |
window.clearInterval(this.launchTimer); | |
window.clearInterval(this.loopTimer); | |
} | |
var that = this; | |
this.launchTimer = window.setInterval(function() { that.launch(); }, 800); | |
this.loopTimer = window.setInterval(function() { that.loop(); }, 1000 / 50); | |
} | |
stop() { | |
window.clearInterval(this.launchTimer); | |
window.clearInterval(this.loopTimer); | |
document.body.removeChild(this.canvas); | |
} | |
} | |
export default Fireworks; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment