Холст HTML5, падающий конфетти / снег, несколько объектов

Я начал с этого примера: http://thecodeplayer.com/walkthrough/html5-canvas-snow-effect

Я пытаюсь добавить несколько форм падающих объектов. Однако, похоже, работает только последний, на который я звоню. Он перезаписывает другие на экране.

Другая вещь, которую я пытаюсь сделать, это рандомизировать цвет из массива, но когда я добавляю случайную функцию, я получаю сумасшедший эффект изменения цвета. Я не до конца понимаю, что происходит, это мое первое погружение в холст. Кажется, что объекты перерисовываются каждый кадр. Что работает для снега, так как это все белые круги.

Любая идея, как я могу внести эти изменения? Вот мой код (ВНИМАНИЕ! ЗАХВАТЫВАТЬ!): 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);

}

person paper_robots    schedule 07.04.2016    source источник
comment
В моем посте есть пример codepen, у вас он не сработал?   -  person paper_robots    schedule 07.04.2016
comment
Нет, я слепой. Мои извинения   -  person QBM5    schedule 07.04.2016


Ответы (2)


Любой цикл анимации на холсте включает повторные вызовы update() и draw(). Вы должны обновить свою модель, а затем рисовать на холсте. Промыть и повторить.

Я создал новый файл Javascript для вашего HTML/CSS. Это позволяет создавать объект Particle, который может быть одного из 3 типов:

  1. Круг
  2. Треугольник
  3. Линия

Затем я создаю массив этих типов объектов. Треть каждого типа заполняется массивом. Затем я просто перебираю эти объекты и вызываю методы обновления и рисования для каждого объекта в моем цикле анимации в моих функциях обновления и рисования соответственно.

Вот код, количество частиц уменьшено до 40, чтобы улучшить плавную скорость анимации:

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();
}
person ManoDestra    schedule 07.04.2016
comment
Это заставляет все три формы появляться друг над другом, но я пытаюсь делать это по одной за раз. Каждый падающий объект представляет собой одну из трех форм. - person paper_robots; 07.04.2016
comment
Затем вам придется выборочно использовать clearRect, я думаю, если вы хотите избежать проблемы перерисовки. Вы не можете очистить весь холст после каждой фигуры, так как вы просто потеряете все, что осталось от предыдущих операций рисования. - person ManoDestra; 07.04.2016
comment
ИЛИ, если каждая частица может быть только одной из трех, тогда вам нужно будет сохранить свойство для каждого объекта частицы, чтобы вы знали, какой это тип, а затем вызвать соответствующую функцию рисования для этого типа частиц. Или, что еще лучше, пусть для каждого типа частиц будут свои собственные методы обновления и отрисовки. Затем, когда вы создаете каждую из них, вы можете добавить их в массив и просто вызвать обновление и нарисовать КАЖДУЮ частицу, что означает, что они будут сами обрабатывать, как рисовать, а не реализовывать для нее какую-либо конструкцию if-else. Все, что вам нужно сделать в этом случае, это передать им ссылку на контекст. - person ManoDestra; 07.04.2016
comment
На данный момент у вас есть только один массив частиц. Вам либо нужно иметь отдельный массив для каждого типа частиц, либо сделать, как я сказал выше, и реализовать разные методы обновления/отрисовки для каждого типа частиц, а затем добавить их в свой массив частиц. Затем попросите их нарисовать себя. Я могу попытаться написать что-нибудь, чтобы отразить это, если это действительно ваше требование? Дай мне знать. - person ManoDestra; 07.04.2016
comment
Я попытался добавить функцию обновления в функции рисования с отдельными массивами частиц для каждой, но это, похоже, не сработало. - person paper_robots; 07.04.2016
comment
Хорошо, заработало. Полностью пересмотрю свой ответ для вас. Производительность не велика, так как мне, вероятно, следует использовать requestAnimationFrame и, возможно, настроить вызовы отрисовки контекста, но, надеюсь, это поможет вам найти решение :) - person ManoDestra; 08.04.2016
comment
Спасибо! Да, это то, что я искал. :] - person paper_robots; 08.04.2016

Вы вызываете функцию drawShapes(), которая рисует 3 фигуры сразу друг за другом. Var ctx является глобальным, поэтому каждое рисование немедленно перезаписывает предыдущее.

person yezzz    schedule 07.04.2016
comment
Должен ли я создавать новую переменную контекста для каждого типа фигуры? - person paper_robots; 07.04.2016
comment
Я не знаю, так как у меня нет опыта работы с холстом. Это было только мое первое наблюдение. Иди с другими парнями за исправлением ;) - person yezzz; 07.04.2016
comment
@yezzz правильно определил вашу ключевую проблему, так что спасибо за это. Вам нужна только одна переменная контекста для вашего холста, поэтому просто используйте ее. Смотрите мой ответ о порядке операций и о том, как лучше всего реализовать вызовы функций :) - person ManoDestra; 07.04.2016