javascripthtmlcanvassnowfall

HTML5 Canvas Falling Confetti / Snow Multiple Objects


I started from this example: http://thecodeplayer.com/walkthrough/html5-canvas-snow-effect

I'm trying to add multiple shapes of falling objects. However only the last one I call seems to work. It overwrites the others on screen.

The other thing I'm trying to do is to randomize the color from the array, but when I add a random function I get a crazy color changing effect. I don't fully understand what's happening, this is my first dive into canvas. It seems like the objects are being redrawn each frame. Which works for the snow since it's all white circles.

Any idea how I can make these changes? Here's my code (SEIZURE WARNING!): http://codepen.io/paper_matthew/pen/vGpyeq

HTML

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

<div id="party-info">

  <h2>PARTY!!</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt impedit animi enim iste repellat aliquam laborum consequuntur asperiores neque eos praesentium quis, consectetur cupiditate suscipit cum inventore excepturi? Vel, laudantium.</p>
</div>

CSS

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  background-color: #fff;
  overflow: hidden;
}

#confetti {
  position: relative;
  top:0;
  left: 0;
  z-index: 1;
}

#party-info {
  position: absolute;
  background: #fff;
  padding: 20px;
  margin: 0 auto;
  height: 200px;
  top: 0;
  left: 0;
  right: 0;
  bottom:0;
  text-align: center;
  width: 400px;
  z-index: 22;
  color: gray;
}

Javascript

window.onload = function() {
  //canvas init
  var canvas = document.getElementById("confetti");
  var ctx = canvas.getContext("2d");

  COLORS = [
    [238, 96, 169],
    [68, 213, 217],
    [245, 187, 152],
    [144, 148, 188],
    [235, 234, 77]
  ];

  //canvas dimensions
  var W = window.innerWidth;
  var H = window.innerHeight;
  canvas.width = W;
  canvas.height = H;

  //particles
  var mp = 100; //max particles
  var particles = [];
  for (var i = 0; i < mp; i++) {
    particles.push({
      x: Math.random() * W, //x-coordinate
      y: Math.random() * H, //y-coordinate
      r: Math.random() * 4 + 1, //radius
      d: Math.random() * mp //density
    })
  }

  // Draw the shapes
  function drawCircle() {

    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = "rgba(" + COLORS[Math.floor(Math.random()*5+0)] + ", 0.8)";
    ctx.beginPath();
    for (var i = 0; i < mp; i++) {
      var p = particles[i];

      ctx.moveTo(p.x, p.y);
      ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2, true);

    }
    ctx.fill();
    update();

  }

  function drawTriangle() {

    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = "rgba(" + COLORS[2] + ", 0.8)";
    ctx.beginPath();
    for (var i = 0; i < mp; i++) {
      var p = particles[i];

      ctx.moveTo(p.x, p.y);
      ctx.lineTo(p.x + 15, p.y);
      ctx.lineTo(p.x + 15, p.y + 15);
      ctx.closePath();

    }
    ctx.fill();
    update();

  }

  function drawLine() {
    ctx.clearRect(0, 0, W, H);
    ctx.strokeStyle = "rgba(" + COLORS[3] + ", 0.8)";
    ctx.beginPath();
    for (var i = 0; i < mp; i++) {
      var p = particles[i];

      ctx.moveTo(p.x, p.y);
      ctx.lineTo(p.x, p.y + 20);
      ctx.lineWidth = 2;

    }
    ctx.stroke();
    update();
  }

  function update() {

    for (var i = 0; i < mp; i++) {
      var p = particles[i];
      p.y += Math.cos(p.d) + 1 + p.r / 2;
      p.x += Math.sin(0) * 2;

      if (p.x > W + 5 || p.x < -5 || p.y > H) {
        particles[i] = {
          x: Math.random() * W,
          y: -10,
          r: p.r,
          d: p.d
        };
      }
    }
  }

  function drawShapes() {
    drawTriangle();
    drawLine();
    drawCircle();
  }

  //animation loop
  setInterval(drawShapes, 33);

}

Solution

  • Any animation loop to the canvas involves repeated calls to update() and draw(). You should update your model, and then draw to the canvas. Rinse and repeat.

    I've created a new Javascript file to go with your HTML/CSS. It allows for creation of a Particle object, which can be one of 3 types:

    1. Circle
    2. Triangle
    3. Line

    I then create an array of these object types. A third of each type is filled into the array. I then simply loop through those objects and call the update and draw methods on each object in my animation loop in my update and draw functions respectively.

    Here's the code, particle count reduced to 40 to improve smooth animation speed:

    function Particle(ctx, width, height, maxParticles, particleType) {
        COLORS = [
            [238, 96, 169],
            [68, 213, 217],
            [245, 187, 152],
            [144, 148, 188],
            [235, 234, 77]
        ];
        var ctx = ctx;
        var width = width;
        var height = height;
        var maxParticles = maxParticles;
        var particleType = particleType;
        var color = COLORS[Math.floor(Math.random() * 5)];
    
        var x = Math.random() * width;
        var y = Math.random() * height;
        var r = Math.random() * 4 + 1;
        var d = Math.random() * maxParticles;
    
        this.update = function() {
            y += Math.cos(d) + 1 + (r / 2);
            x += Math.sin(0) * 2;
            if (x > width + 5 || x < -5 || y > height) {
                x = Math.random() * width;
                y = -10;
            }
        };
    
        this.draw = function() {
            ctx.save();
            ctx.strokeStyle = "rgba(" + color + ", 0.8)";
            ctx.fillStyle = ctx.strokeStyle;
            ctx.beginPath();
            for (var i = 0; i < maxParticles; i++) {
                ctx.moveTo(x, y);
                switch (particleType) {
                case 1:
                    ctx.arc(x, y, r, 0, Math.PI * 2, false);
                    ctx.fill();
                    break;
                case 2:
                    ctx.lineTo(x + 15, y);
                    ctx.lineTo(x + 15, y + 15);
                    ctx.fill();
                    break;
                case 3:
                    ctx.lineWidth = 2;
                    ctx.lineTo(x, y + 20);
                    ctx.stroke();
                    break;
                default:
                    console.log('Unable to draw: undefined particle type [' + particleType + ']');
                    break;
                }
            }
    
            ctx.restore();
        };
    }
    
    window.onload = function() {
        //canvas init
        var canvas = document.getElementById("confetti");
        var ctx = canvas.getContext("2d");
    
        //canvas dimensions
        var W = window.innerWidth;
        var H = window.innerHeight;
        canvas.width = W;
        canvas.height = H;
    
        //particles
        var mp = 40;
        var particles = [];
        for (var i = 0; i < mp; i++) {
            var type = Math.floor(i * 3 / mp) + 1;
            particles.push(new Particle(ctx, W, H, particles.length, type));
        }
    
        function drawShapes() {
            update();
            draw();
            setTimeout(drawShapes, 20);
        }
    
        function update() {
            for (var key in particles) {
                particles[key].update();
            }
        }
    
        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            for (var key in particles) {
                particles[key].draw();
            }
        }
    
        //animation loop
        drawShapes();
    }