Skip to content

Instantly share code, notes, and snippets.

@JeffreyHyer
Created June 19, 2017 14:54
Show Gist options
  • Save JeffreyHyer/9a9a684837129676738c8807fe4cf599 to your computer and use it in GitHub Desktop.
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.
/**
* 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