A Pen by Riaz Laskar on CodePen.
Created
June 12, 2019 18:13
-
-
Save jasonnoble/9b844151726c7aab9af5b9b902a2ffbc to your computer and use it in GitHub Desktop.
Birthday wish
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
<canvas id="canvas"></canvas> |
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
// when animating on canvas, it is best to use requestAnimationFrame instead of setTimeout or setInterval | |
// not supported in all browsers though and sometimes needs a prefix, so we need a shim | |
window.requestAnimFrame = ( function() { | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
function( callback ) { | |
window.setTimeout( callback, 1000 / 60 ); | |
}; | |
})(); | |
// now we will setup our basic variables for the demo | |
var canvas = document.getElementById( 'canvas' ), | |
ctx = canvas.getContext( '2d' ), | |
// full screen dimensions | |
cw = window.innerWidth, | |
ch = window.innerHeight, | |
// firework collection | |
fireworks = [], | |
// particle collection | |
particles = [], | |
// starting hue | |
hue = 120, | |
// when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks | |
limiterTotal = 5, | |
limiterTick = 0, | |
// this will time the auto launches of fireworks, one launch per 80 loop ticks | |
timerTotal = 80, | |
timerTick = 0, | |
mousedown = false, | |
// mouse x coordinate, | |
mx, | |
// mouse y coordinate | |
my; | |
// set canvas dimensions | |
canvas.width = cw; | |
canvas.height = ch; | |
// now we are going to setup our function placeholders for the entire demo | |
// get a random number within a range | |
function random( min, max ) { | |
return Math.random() * ( max - min ) + min; | |
} | |
// calculate the distance between two points | |
function calculateDistance( p1x, p1y, p2x, p2y ) { | |
var xDistance = p1x - p2x, | |
yDistance = p1y - p2y; | |
return Math.sqrt( Math.pow( xDistance, 2 ) + Math.pow( yDistance, 2 ) ); | |
} | |
// create firework | |
function Firework( sx, sy, tx, ty ) { | |
// actual coordinates | |
this.x = sx; | |
this.y = sy; | |
// starting coordinates | |
this.sx = sx; | |
this.sy = sy; | |
// target coordinates | |
this.tx = tx; | |
this.ty = ty; | |
// distance from starting point to target | |
this.distanceToTarget = calculateDistance( sx, sy, tx, ty ); | |
this.distanceTraveled = 0; | |
// track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails | |
this.coordinates = []; | |
this.coordinateCount = 3; | |
// populate initial coordinate collection with the current coordinates | |
while( this.coordinateCount-- ) { | |
this.coordinates.push( [ this.x, this.y ] ); | |
} | |
this.angle = Math.atan2( ty - sy, tx - sx ); | |
this.speed = 2; | |
this.acceleration = 1.05; | |
this.brightness = random( 50, 70 ); | |
// circle target indicator radius | |
this.targetRadius = 1; | |
} | |
// update firework | |
Firework.prototype.update = function( index ) { | |
// remove last item in coordinates array | |
this.coordinates.pop(); | |
// add current coordinates to the start of the array | |
this.coordinates.unshift( [ this.x, this.y ] ); | |
// cycle the circle target indicator radius | |
if( this.targetRadius < 8 ) { | |
this.targetRadius += 0.3; | |
} else { | |
this.targetRadius = 1; | |
} | |
// speed up the firework | |
this.speed *= this.acceleration; | |
// get the current velocities based on angle and speed | |
var vx = Math.cos( this.angle ) * this.speed, | |
vy = Math.sin( this.angle ) * this.speed; | |
// how far will the firework have traveled with velocities applied? | |
this.distanceTraveled = calculateDistance( this.sx, this.sy, this.x + vx, this.y + vy ); | |
// if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached | |
if( this.distanceTraveled >= this.distanceToTarget ) { | |
createParticles( this.tx, this.ty ); | |
// remove the firework, use the index passed into the update function to determine which to remove | |
fireworks.splice( index, 1 ); | |
} else { | |
// target not reached, keep traveling | |
this.x += vx; | |
this.y += vy; | |
} | |
} | |
// draw firework | |
Firework.prototype.draw = function() { | |
ctx.beginPath(); | |
// move to the last tracked coordinate in the set, then draw a line to the current x and y | |
ctx.moveTo( this.coordinates[ this.coordinates.length - 1][ 0 ], this.coordinates[ this.coordinates.length - 1][ 1 ] ); | |
ctx.lineTo( this.x, this.y ); | |
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)'; | |
ctx.stroke(); | |
ctx.beginPath(); | |
// draw the target for this firework with a pulsing circle | |
ctx.arc( this.tx, this.ty, this.targetRadius, 0, Math.PI * 2 ); | |
ctx.stroke(); | |
} | |
// create particle | |
function Particle( x, y ) { | |
this.x = x; | |
this.y = y; | |
// track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails | |
this.coordinates = []; | |
this.coordinateCount = 5; | |
while( this.coordinateCount-- ) { | |
this.coordinates.push( [ this.x, this.y ] ); | |
} | |
// set a random angle in all possible directions, in radians | |
this.angle = random( 0, Math.PI * 2 ); | |
this.speed = random( 1, 10 ); | |
// friction will slow the particle down | |
this.friction = 0.95; | |
// gravity will be applied and pull the particle down | |
this.gravity = 1; | |
// set the hue to a random number +-20 of the overall hue variable | |
this.hue = random( hue - 20, hue + 20 ); | |
this.brightness = random( 50, 80 ); | |
this.alpha = 1; | |
// set how fast the particle fades out | |
this.decay = random( 0.015, 0.03 ); | |
} | |
// update particle | |
Particle.prototype.update = function( index ) { | |
// remove last item in coordinates array | |
this.coordinates.pop(); | |
// add current coordinates to the start of the array | |
this.coordinates.unshift( [ this.x, this.y ] ); | |
// slow down the particle | |
this.speed *= this.friction; | |
// apply velocity | |
this.x += Math.cos( this.angle ) * this.speed; | |
this.y += Math.sin( this.angle ) * this.speed + this.gravity; | |
// fade out the particle | |
this.alpha -= this.decay; | |
// remove the particle once the alpha is low enough, based on the passed in index | |
if( this.alpha <= this.decay ) { | |
particles.splice( index, 1 ); | |
} | |
} | |
// draw particle | |
Particle.prototype.draw = function() { | |
ctx. beginPath(); | |
// move to the last tracked coordinates in the set, then draw a line to the current x and y | |
ctx.moveTo( this.coordinates[ this.coordinates.length - 1 ][ 0 ], this.coordinates[ this.coordinates.length - 1 ][ 1 ] ); | |
ctx.lineTo( this.x, this.y ); | |
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')'; | |
ctx.stroke(); | |
} | |
// create particle group/explosion | |
function createParticles( x, y ) { | |
// increase the particle count for a bigger explosion, beware of the canvas performance hit with the increased particles though | |
var particleCount = 30; | |
while( particleCount-- ) { | |
particles.push( new Particle( x, y ) ); | |
} | |
} | |
// main demo loop | |
function loop() { | |
// this function will run endlessly with requestAnimationFrame | |
requestAnimFrame( loop ); | |
// increase the hue to get different colored fireworks over time | |
hue += 0.5; | |
// normally, clearRect() would be used to clear the canvas | |
// we want to create a trailing effect though | |
// setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely | |
ctx.globalCompositeOperation = 'destination-out'; | |
// decrease the alpha property to create more prominent trails | |
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
ctx.fillRect( 0, 0, cw, ch ); | |
// change the composite operation back to our main mode | |
// lighter creates bright highlight points as the fireworks and particles overlap each other | |
ctx.globalCompositeOperation = 'lighter'; | |
var text = "HAPPY BIRTHDAY RIYA !!"; | |
ctx.font = "50px sans-serif"; | |
var textData = ctx.measureText(text); | |
ctx.fillStyle = "rgba("+parseInt(random(0,255))+","+parseInt(random(0,255))+","+parseInt(random(0,255))+",0.3)"; | |
ctx.fillText(text,cw /2-textData.width/2,ch/2); | |
// loop over each firework, draw it, update it | |
var i = fireworks.length; | |
while( i-- ) { | |
fireworks[ i ].draw(); | |
fireworks[ i ].update( i ); | |
} | |
// loop over each particle, draw it, update it | |
var i = particles.length; | |
while( i-- ) { | |
particles[ i ].draw(); | |
particles[ i ].update( i ); | |
} | |
// launch fireworks automatically to random coordinates, when the mouse isn't down | |
if( timerTick >= timerTotal ) { | |
if( !mousedown ) { | |
// start the firework at the bottom middle of the screen, then set the random target coordinates, the random y coordinates will be set within the range of the top half of the screen | |
for(var h=0;h<50;h++) | |
{ | |
fireworks.push( new Firework( cw / 2, ch/2, random( 0, cw ), random( 0, ch ) ) ); | |
} | |
timerTick = 0; | |
} | |
} else { | |
timerTick++; | |
} | |
// limit the rate at which fireworks get launched when mouse is down | |
if( limiterTick >= limiterTotal ) { | |
if( mousedown ) { | |
// start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target | |
fireworks.push( new Firework( cw / 2, ch/2, mx, my ) ); | |
limiterTick = 0; | |
} | |
} else { | |
limiterTick++; | |
} | |
} | |
// mouse event bindings | |
// update the mouse coordinates on mousemove | |
canvas.addEventListener( 'mousemove', function( e ) { | |
mx = e.pageX - canvas.offsetLeft; | |
my = e.pageY - canvas.offsetTop; | |
}); | |
// toggle mousedown state and prevent canvas from being selected | |
canvas.addEventListener( 'mousedown', function( e ) { | |
e.preventDefault(); | |
mousedown = true; | |
}); | |
canvas.addEventListener( 'mouseup', function( e ) { | |
e.preventDefault(); | |
mousedown = false; | |
}); | |
// once the window loads, we are ready for some fireworks! | |
window.onload = loop; | |
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
/* basic styles for black background and crosshair cursor */ | |
body { | |
background: #000; | |
margin: 0; | |
} | |
canvas { | |
cursor: crosshair; | |
display: block; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment