1

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);

}
paper_robots
  • 251
  • 2
  • 15

2 Answers2

1

You're calling drawShapes() which draws the 3 shapes immediately after each other. Var ctx is global, so each draw immediately overwrites the previous one.

yezzz
  • 2,990
  • 1
  • 10
  • 19
  • Should I be creating a new context variable for each shape type? – paper_robots Apr 07 '16 at 19:59
  • I don't know as I have no experience with canvas either. This was just my first observation. Go with the other guys for a fix ;) – yezzz Apr 07 '16 at 20:01
  • @yezzz correctly identified your key issue, so kudos for that. You only need a single context variable for your canvas, so just use that. See my answer regarding order of operations and how best to implement your function calls :) – ManoDestra Apr 07 '16 at 20:02
1

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();
}
ManoDestra
  • 6,325
  • 6
  • 26
  • 50
  • That gets all three shapes to show up on top of each other, but I'm trying to do one at a time. With each falling object being one of the three shapes. – paper_robots Apr 07 '16 at 20:02
  • Then, you're going to have to selectively use clearRect, I guess, if you wish to avoid the overdrawing issue. You can't clear the whole canvas after each shape, as you'll just lose everything from the previous draw operations. – ManoDestra Apr 07 '16 at 20:05
  • OR, if each particle can only be one of the three, then you'll have to store a property on each particle object so that you know which kind it is, then call the appropriate draw function for that kind of particle. Or, better still, have each particle type have its own update and draw methods. Then, when you create each one, you can add them to an array and just call update and draw on EACH particle, meaning they will then handle how to draw themselves, rather than implementing any if-else construct for it. All you need to do in that instance is to pass a reference to the context to them. – ManoDestra Apr 07 '16 at 20:09
  • At the moment, you only have one array called particles. You either need to have a separate array for each type of particle, or do as I stated above and implement different update/draw methods for each particle type and then add them to your particles array. Then ask them to draw themselves. I can try to write something to reflect this, if this is really your requirement? Let me know. – ManoDestra Apr 07 '16 at 20:15
  • I tried adding the update function into the draw functions with separate particle arrays for each but that did not seem to work. – paper_robots Apr 07 '16 at 20:27
  • Ok, got it working. Will completely revise my answer for you. Performance isn't great, as I probably should be using requestAnimationFrame and perhaps tweaking the context draw calls, but hopefully this will help get you on the road to a solution :) – ManoDestra Apr 07 '16 at 21:37
  • Thanks! Yeah this is what I was looking for. :] – paper_robots Apr 08 '16 at 12:47