Конфетти на канвасе

    Привет Хабр! Попалась недавно интересная вещичка , которая создает эффект конфетти на страничке. Решил глянуть , что же там внутри находится, как работает и познакомиться с канвасом поближе. Подробности под катом.

    Вступление

    Повествование будет вестись с точки зрения человека, который первый раз в жизни увидел канвас ( ладно, второй, первой была вот эта статья с хабра про фрактальные снежинки) и хочет почерпнуть себе разные полезные моменты, которые , возможно, захочется использовать в будущем. Отталкивался от исходного кода библиотечки canvas-confetti.

    Настройки

    Удобно представить себе некую пушку, которая находится в некой точке origin, наклонена под углом angle и стреляет зарядом в виде конуса, который отклоняется от направления выстрела влево и вправо на угол spread со скоростью startVelocity . Потом частицы начинают терять скорость в зависимости от сопротивления воздуха decay и падать под действием силы тяжести gravity. Еще есть параметры для колличества частиц, цвета, формы, размера ( particleCount, colors, shapes, scalar). Достаточно добавить только интересующие опции, остальные подтянутся по умолчанию.

    confetti({
      particleCount: 100,
      startVelocity: 30,
      spread: 360,
      origin: {
        x: Math.random(),
        y: Math.random() - 0.2
      }
    }

    Полезные моменты внутри

    Штука, которая обеспечивает 60 кадров в секунду с помощью requestAnimationFrame , если есть или откатывается к setTimeout

    var raf = (function () {
      var TIME = Math.floor(1000 / 60);
      var frame, cancel;
      var frames = {};
      var lastFrameTime = 0;
    
      if (typeof requestAnimationFrame === 'function' && typeof cancelAnimationFrame === 'function') {
        frame = function (cb) {
          var id = Math.random();
    
          frames[id] = requestAnimationFrame(function onFrame(time) {
            if (lastFrameTime === time || lastFrameTime + TIME - 1 < time) {
              lastFrameTime = time;
              delete frames[id];
    
              cb();
            } else {
              frames[id] = requestAnimationFrame(onFrame);
            }
          });
    
          return id;
        };
        cancel = function (id) {
          if (frames[id]) {
            cancelAnimationFrame(frames[id]);
          }
        };
      } else {
        frame = function (cb) {
          return setTimeout(cb, TIME);
        };
        cancel = function (timer) {
          return clearTimeout(timer);
        };
      }
    
      return { frame: frame, cancel: cancel };
    }());

    Заполнение канвасом всей видимой области странички с помощью createElement, appendChild, clientWidth, clientHeight

    function getCanvas(zIndex) {
        var canvas = document.createElement('canvas');
    
        canvas.style.position = 'fixed';
        canvas.style.top = '0px';
        canvas.style.left = '0px';
        canvas.style.pointerEvents = 'none';
        canvas.style.zIndex = zIndex;
    
        return canvas;
      }
    // ..... 
    document.body.appendChild(canvas);
    // .....
    function setCanvasWindowSize(canvas) {
      canvas.width = document.documentElement.clientWidth;
      canvas.height = document.documentElement.clientHeight;
    }
    

    Получение двумерного контекста канваса с getContext

    var context = canvas.getContext('2d');

    Чистка, которая происходит перед отрисовкой каждого кадра с помощью clearRect в методе update

    context.clearRect(0, 0, size.width, size.height);

    Для создания каждого кадра вызывается update, внутри которого для каждой "конфетиточки" вызывается код, который считает ее геометрические координаты и рисует ее с помощью методов контекста beginPath, moveTo, lineTo, closePath и fill. Также каждая фетишка отслеживает сколько у нее прошло кадров-апдейтов и потом, когда у всех фетишек закончатся кадры, анимация отрапортует о своем завершении.

    function updateFetti(context, fetti) {
    		// ...
        // пара десятков строк косинусов и синусов, которые посчитают новые координаты для конфетишки
        // ...
    
        context.fillStyle = 'rgba(' + fetti.color.r + ', ' + fetti.color.g + ', ' + fetti.color.b + ', ' + (1 - progress) + ')';
        context.beginPath();
    
        // ...
        context.moveTo(Math.floor(fetti.x), Math.floor(fetti.y));
        context.lineTo(Math.floor(fetti.wobbleX), Math.floor(y1));
        context.lineTo(Math.floor(x2), Math.floor(y2));
        context.lineTo(Math.floor(x1), Math.floor(fetti.wobbleY));
    
        context.closePath();
        context.fill();
    		
        // ...
    		// когда кадры закончатся фетишка отфильтруется из массива частиц для апдейта
        return fetti.tick < fetti.totalTicks;
      }

    Заключение

    Надеюсь этот пост поможет кому-нибудь быстро ознакомится с рисованием на двумерном канвасе. В исходнике есть много полезного касательно анимации из воркера и геометрии. Желающие могут ознакомиться более детально по ссылке вначале статьи.

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое