Interactive Confetti Animation In JavaScript And Canvas

Interactive Confetti Animation In JavaScript And Canvas
File Size: 8.86 KB
Views Total: 880
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   

Pretty awesome confetti falling & explosion effects created with jQuery, VerletExpressJS (a tiny physics engine) and HTML5 canvas.

Features:

  • Creates interactive moving & falling confetti that reacts to the viewer's cursor.
  • Triggers confetti explosions on click/tap.

How to use it:

1. Create a canvas element on which you want to draw the confettis.

<canvas id="canvas"></canvas>

2. Include jQuery and VerletExpressJS libraries from CDN.

<script src="/path/to/cdn/jquery.min.js"></script>
<script src="/path/to/cdn/verletExpress.min.js"></script>

3. The main script to enable the interactive confettis.

$( window ).on( "load", ()=> {
    
  // Elements
  var canvas = document.getElementById("canvas");
  var canvasWidth = canvas.clientWidth;
  var canvasHeight = canvas.clientHeight

  // VerletExpressJS
  VX.initialize( "2d", "canvas", "canvas", canvasWidth, canvasHeight );
  VX.xRange = { min: null, max: null };
  VX.yRange = { min: -VX.interfaceHeight*0.2, max: null };
  VX.breeze = 1;

  // Settings
  var beginWithBurst = true;
  var confettis = []; var confettiCount = 0;
  var confettiVolumeInit = 150;
  var confettiVolumeMax = confettiVolumeInit+25;
  var confettiWidthMin = VX.interfaceHeight*0.01;
  var confettiWidthMax = VX.interfaceHeight*0.015;
  var confettiWidth = function() { return VX.rfb( confettiWidthMin, confettiWidthMax );  };
  var confettiInitX = function() { return VX.rfb( 0, VX.interfaceWidth ) }; // initial confetti top left corner x position
  var confettiInitY = function() { return VX.rfb( -confettiWidthMax, VX.interfaceHeight ) }; // initial confetti top left corner y position
  var confettiVelocityBase = confettiWidthMax*0.5;
  var initialBurstIntensity = confettiWidthMax*5;
  var refreshed = false; 

  // colors
  var backgroundColor = "#111111";
  var confettiAlpha = 1;  // confetti alpha
  var janeStreetPalette = [ `rgba( 32, 133, 239, ${confettiAlpha} )`,
                            `rgba( 0, 194, 143, ${confettiAlpha} )`,
                            `rgba( 129, 208, 4, ${confettiAlpha} )`,
                            `rgba( 86, 171, 15, ${confettiAlpha} )` ];
  var pastelPartyPalette = [ `rgba( 168, 100, 253, ${confettiAlpha} )`,
                             `rgba( 41, 205, 255, ${confettiAlpha} )`,
                             `rgba( 120, 255, 68, ${confettiAlpha} )`,
                             `rgba( 253, 255, 106, ${confettiAlpha} )`,
                             `rgba( 255, 113, 141, ${confettiAlpha} )` ];
  var tropicalSunsetPalette = [ `rgba( 254,218,132, ${confettiAlpha} )`,
                                `rgba( 255,155,131, ${confettiAlpha} )`,
                                `rgba( 151,99,147, ${confettiAlpha} )`,
                                `rgba( 104,84,137, ${confettiAlpha} )`,
                                `rgba( 67,69,127, ${confettiAlpha} )` ];
  var goldPalette = [ `rgba( 166,124,0, ${confettiAlpha} )`,
                      `rgba( 191,155,48, ${confettiAlpha} )`,
                      `rgba( 255,191,0, ${confettiAlpha} )`,
                      `rgba( 255,207,64, ${confettiAlpha} )`,
                      `rgba( 255,220,115, ${confettiAlpha} )` ];
  var randomPalette = generateRandomPalette(5);
  document.getElementById("canvas").style.background = backgroundColor;
  var colors = randomPalette;

  // Interaction
  var mouseCoords = { cx: null, cy: null, px: null, py: null };
  var mouseZoneRadius = confettiWidthMax*5;
  var mouseStrength = 0.1;
  var popperBurstVolumeMax = 50;
  var popperBurstIntensity = confettiWidthMax*2;
  var popperBurstZoneRadius = confettiWidthMax*10;

  /////--- Functions ---/////


  function generateRandomPalette( colorCount ) {
    var palette = [];
    for ( var i=0; i<colorCount; i++ ) { 
      palette.push( `rgba( ${VX.rib(0,255)}, ${VX.rib(0,255)}, ${VX.rib(0,255)}, ${confettiAlpha} )` ); 
    }
    return palette
  }
    

  function Confetti( initX, initY ) {
    confettiCount++;
    this.id = confettiCount;
    this.width = confettiWidth();
    this.color = colors[ VX.rib( 0, colors.length-1 ) ];
    this.pt1 = VX.addPoint( { x: initX, y: initY } );
    this.pt1.px += VX.rfb( -confettiVelocityBase/2, confettiVelocityBase/2 );  // adds initialx velocity
    this.pt1.py += VX.rfb( -confettiVelocityBase, 0 );  // adds initial y velocity
    this.pt2 = VX.addPoint( { x: initX+this.width, y: initY } );
    this.pt3 = VX.addPoint( { x: initX+this.width, y: initY+this.width } );
    this.pt4 = VX.addPoint( { x: initX, y: initY+this.width } );
    this.sp1 = VX.addSpan( this.pt1, this.pt2 );
    this.sp2 = VX.addSpan( this.pt2, this.pt3 );
    this.sp3 = VX.addSpan( this.pt3, this.pt4 );
    this.sp4 = VX.addSpan( this.pt4, this.pt1 );
    this.sp5 = VX.addSpan( this.pt1, this.pt3 );
    this.sk = VX.addSkin( [ this.pt1, this.pt2, this.pt3, this.pt4 ], { fillColor: this.color, outlineColor: "rgba(0,0,0,0)", outlineThickness: 0 } );
  }

  function runInitialBurst() {  
    for ( var i=0; i<confettiVolumeInit; i++ ) {
      var initAngle = VX.rfb(0,Math.PI*2);
      var initDistance = VX.rfb( 0, VX.interfaceWidth*0.1 );
      var initConfettiX = VX.interfaceWidth*0.5 + Math.cos( initAngle - Math.PI/2 ) * initDistance;
      var initConfettiY = VX.interfaceHeight*0.6 + Math.sin( initAngle - Math.PI/2 ) * initDistance;
      confettis.push( new Confetti( initConfettiX, initConfettiY ) );
      var velocity = VX.rfb( -initialBurstIntensity, initialBurstIntensity );
      var velocityAngle = VX.rfb( 0, Math.PI*2 );
      confettis[confettis.length-1].pt1.px = initConfettiX + Math.cos( velocityAngle-Math.PI/2 ) * velocity;
      confettis[confettis.length-1].pt1.py = initConfettiY + Math.sin( velocityAngle-Math.PI/2 ) * velocity + VX.interfaceWidth*0.01;
    }
  }

  function runInitialFall() {
    for ( var i=0; i<confettiVolumeInit; i++ ) {
      confettis.push( new Confetti( confettiInitX(), confettiInitY() ) );
    }
    confettiInitY = function() { return -confettiWidthMax*3; };
  }

  function beginConfetti() {
    beginWithBurst ? runInitialBurst() : runInitialFall();
    confettiInitY = ()=> { return -confettiWidthMax*3; };
    setTimeout( ()=> { refreshed = false }, 1 );
  }

  function removeConfettiById( id ) {
    for ( var i=0; i<confettis.length; i++ ){ 
      var c = confettis[i]; 
      if ( c.id == id ) { 
        VX.removePoint( c.pt1.id ); VX.removePoint( c.pt2.id ); VX.removePoint( c.pt3.id ); VX.removePoint( c.pt4.id );
        VX.removeSpan( c.sp1.id ); VX.removeSpan( c.sp2.id ); VX.removeSpan( c.sp3.id ); VX.removeSpan( c.sp4.id ); VX.removeSpan( c.sp5.id ); 
        VX.removeSkin( c.sk.id );
        confettis.splice(i,1); 
      }
    }
  }

  function removeConfettiByIndex( index ) {
    var c = confettis[index];
    VX.removePoint( c.pt1.id ); VX.removePoint( c.pt2.id ); VX.removePoint( c.pt3.id ); VX.removePoint( c.pt4.id );
    VX.removeSpan( c.sp1.id ); VX.removeSpan( c.sp2.id ); VX.removeSpan( c.sp3.id ); VX.removeSpan( c.sp4.id );   
    VX.removeSpan( c.sp5.id ); 
    VX.removeSkin( c.sk.id );
    confettis.splice( index, 1 );
  }

  function replaceFallenConfettis() {
    for ( var i=0; i<confettis.length; i++ ) {
      var c = confettis[i];  
      if ( c.pt1.cy > VX.interfaceHeight*1.1) {
        removeConfettiById( c.id );
        if ( confettis.length < confettiVolumeInit ) {
          confettis.push( new Confetti( confettiInitX(), confettiInitY() ) );
        }
      }
    }
  }

  function updateMouseCurrentCoords( event ) { 
    mouseCoords.cx = event.clientX; 
    mouseCoords.cy = event.clientY;
  }

  function updateMousePreviousCoords() {
    mouseCoords.px = mouseCoords.cx;
    mouseCoords.py = mouseCoords.cy;  
  }

  function moveConfettisOnMouseOver() {
    for ( var i=0; i<confettis.length; i++ ) {
      var c = confettis[i];
      var xDiff = Math.abs( mouseCoords.cx - c.pt1.cx );
      var yDiff = Math.abs( mouseCoords.cy - c.pt1.cy );
      var mouseVelocityX = mouseCoords.cx - mouseCoords.px;
      var mouseVelocityY = mouseCoords.cy - mouseCoords.py;
      if ( xDiff <= mouseZoneRadius && yDiff <= mouseZoneRadius && mouseCoords.px != null && mouseCoords.py != null ) {
        c.pt1.px -= mouseVelocityX*mouseStrength;
        c.pt1.py -= mouseVelocityY*mouseStrength;
      }
    }
  }

  function popperBurst( event ) {  
    // blast nearby confettis
    for ( var i=0; i<confettis.length; i++ ) {
      var c = confettis[i];
      var xDiff = c.pt1.cx - mouseCoords.cx;
      var yDiff = c.pt1.cy - mouseCoords.cy;
      var dist = Math.sqrt( Math.abs(xDiff*xDiff) + Math.abs(yDiff*yDiff) );
      var intensity = (popperBurstZoneRadius-dist)*popperBurstIntensity*0.0005;
      if ( dist <= popperBurstZoneRadius ) {
        c.pt1.px = c.pt1.cx - xDiff*intensity;
        c.pt1.py = c.pt1.cy - yDiff*intensity;
      }
    }
    // new confettis burst
    var volume = confettis.length < confettiVolumeMax-popperBurstVolumeMax ? popperBurstVolumeMax : confettiVolumeMax-confettis.length; 
    for ( var i=0; i<volume; i++ ) {
      confettis.push( new Confetti( mouseCoords.cx, mouseCoords.cy ) );
      confettis[confettis.length-1].pt1.px = mouseCoords.cx;
      confettis[confettis.length-1].pt1.py = mouseCoords.cy;
      var velocity = VX.rfb( -popperBurstIntensity, popperBurstIntensity );
      var angle = VX.rfb( 0, Math.PI*2 );
      confettis[confettis.length-1].pt1.px = mouseCoords.cx + Math.cos( angle-Math.PI/2 ) * velocity;
      confettis[confettis.length-1].pt1.py = mouseCoords.cy + Math.sin( angle-Math.PI/2 ) * velocity;
    }
  }
    
  function refresh() {
    VX.points = [];
    VX.spans = [];
    VX.skins = [];
    confettis = [];
    colors = colors = randomPalette ? generateRandomPalette(5) : colors;
    refreshed = true;
    beginConfetti();
  }



  /////--- Adapted Functions ---/////

  var mv = 5;  // maximum velocity

  VX.updatePoints = function() {
    for ( var i=0; i<VX.points.length; i++ ) {
      var p = VX.points[i];
      var xv = ( p.cx - p.px ) * VX.friction;
      var yv = ( p.cy - p.py ) * VX.friction;
      p.px = p.cx;
      p.py = p.cy;
      p.cx += xv; 
      p.cy += yv; 
      p.cy += VX.gravity * p.mass;
      if ( VX.worldTime % VX.rib( 100, 200 ) == 0 ) { p.cx += VX.rfb( -VX.breeze, VX.breeze ); }  
    }
  }
      
  VX.runOnFrameRefresh = function() { 
    replaceFallenConfettis();
    moveConfettisOnMouseOver();
    updateMousePreviousCoords();
  }



  /////--- Scaling ---/////
    
  function scaleToWindow() {
    VX.interfaceWidth = VX.canvas.width = window.innerWidth;
    VX.interfaceHeight = VX.canvas.height = window.innerHeight;
  }

  scaleToWindow();
      
  /////--- Events ---/////

  window.addEventListener( "resize", scaleToWindow );
  document.addEventListener( "mousedown", ()=> { if ( !refreshed ) popperBurst() });
  document.addEventListener( "mousemove", updateMouseCurrentCoords );

  /////--- Run ---/////

  beginConfetti();

});

This awesome jQuery plugin is developed by matthewmain. For more Advanced Usages, please check the demo page or visit the official website.