<body>
  <div id="canvas__container">
    <div id="canvas__wrapper">
      <canvas id="canvas" class="canvas"></canvas>
    </div>
  </div>
</body>
body {
  margin: 0
}

.canvas {
  // border: 1px solid black
}

#canvas__container {
  width: 100vw
  height: 100vh
  background-color: white
  position: relative
  z-index: 1
}

#canvas__wrapper {
  position: absolute
  width: 100%
  height: 100%
  background-color: white
  z-index: -1
}
class Circle {

  constructor(color, radius) {
    this.color = color;
    this.radius = radius;
    this.fixedX = "left";
    this.fixedY = "top";
    this.scale = 1;
    this.isWiggling = true;
  }

  setOriginalPosition(x, y) {
    // Origin coordinates should be set as pixels
    // These coordinates will be intreprested through the lens of where this
    // object is fixed.
    this.origin = {x, y};
    this.x = x;
    this.y = y;
  }

  calculateRelativePosition(ctx) {
    // This will return the calculated position of this object within the
    // given canvas context based on where this object is fixed.
    const height = ctx.canvas.height;
    const width = ctx.canvas.width;
    const pos = {
      x: this.x,
      y: this.y
    };

    if (this.fixedX === "right") {
      pos.x += width;
    }

    if (this.fixedY === "bottom") {
      pos.y += height;
    }

    return pos;
  }

  calculateRadius() {
    return this.radius * this.scale;
  }

}


class Canvas {
  constructor() {

    const desktopData = [
      {size: 20, fx: "left", x: 250, fy: "top", y: 240, color: "rgba(51, 145, 148, .5)"},
      {size: 70, fx: "left", x: 200, fy: "top", y: 120, color: "rgba(30, 129, 127, .5)"},
      {size: 130, fx: "left", x: 70, fy: "top", y: 40, color: "rgba(167, 2, 103, .5)"},
      {size: 50, fx: "left", x: 70, fy: "top", y: 170, color: "rgba(246, 216, 107, .5)"},
      {size: 90, fx: "left", x: -15, fy: "top", y: 170, color: "rgba(192, 231, 109, .5)"},
      {size: 10, fx: "left", x: 30, fy: "top", y: 270, color: "rgba(251, 107, 65, .5)"},
      {size: 240, fx: "left", x: 620, fy: "bottom", y: -320, color: "rgba(241, 12, 73, .5)"},
      {size: 180, fx: "left", x: 500, fy: "bottom", y: -340, color: "rgba(42, 148, 132, .5)"},
      {size: 160, fx: "left", x: 760, fy: "bottom", y: -180, color: "rgba(246, 216, 107, .5)"},
      {size: 110, fx: "left", x: 830, fy: "bottom", y: -320, color: "rgba(251, 107, 65, .5)"},
      {size: 50, fx: "left", x: 980, fy: "bottom", y: -500, color: "rgba(192, 231, 109, .5)"},
      {size: 15, fx: "left", x: 540, fy: "bottom", y: -620, color: "rgba(251, 107, 65, .5)"},
      {size: 20, fx: "right", x: -230, fy: "top", y: 240, color: "rgba(167, 2, 103, .5)"},
      {size: 22, fx: "right", x: -100, fy: "top", y: 180, color: "rgba(192, 231, 109, .5)"},
      {size: 22, fx: "right", x: -140, fy: "top", y: 400, color: "rgba(241, 12, 73, .5)"}
    ];

    const mobileData = [
      {size: 20, fx: "right", x: -40, fy: "top", y: 690, color: "rgba(30, 129, 127, .5)"},
      {size: 130, fx: "right", x: -50, fy: "top", y: 470, color: "rgba(167, 2, 103, .5)"},
      {size: 50, fx: "right", x: -70, fy: "top", y: 360, color: "rgba(246, 216, 107, .5)"},
      {size: 90, fx: "right", x: 15, fy: "top", y: 370, color: "rgba(167, 2, 104, .5)"},
      {size: 180, fx: "left", x: 0, fy: "bottom", y: -330, color: "rgba(241, 12, 73, .5)"},
      {size: 110, fx: "left", x: -85, fy: "bottom", y: -300, color: "rgba(42, 148, 132, .5)"},
      {size: 120, fx: "left", x: 15, fy: "bottom", y: -470, color: "rgba(246, 216, 107, .5)"},
      {size: 90, fx: "left", x: 150, fy: "bottom", y: -400, color: "rgba(251, 107, 65, .5)"},
      {size: 170, fx: "right", x: 20, fy: "bottom", y: -60, color: "rgba(51, 145, 148, .5)"},
      {size: 50, fx: "right", x: -20, fy: "bottom", y: -600, color: "rgba(192, 231, 109, .5)"},
      {size: 20, fx: "right", x: -80, fy: "bottom", y: -650, color: "rgba(167, 2, 103, .5)"},
      {size: 90, fx: "right", x: -150, fy: "bottom", y: -70, color: "rgba(251, 107, 65, .5)"},
      {size: 110, fx: "right", x: -70, fy: "bottom", y: -200, color: "rgba(246, 216, 107, .5)"}
    ];

    const ctx = this.instantiateCanvas();
    let circles = [];

    // circles = this.instantiateCircles(mobileData);
    circles = this.instantiateCircles(desktopData);

    window.addEventListener("resize", () => {
      this.resizeCanvas(ctx);
      this.step(circles, ctx);
    });
    this.resizeCanvas(ctx);

    TweenLite.ticker.addEventListener("tick", () => { this.draw(circles, ctx); });

    this.step(circles, ctx);
  }

  instantiateCanvas() {
    const ctx = document.getElementById("canvas").getContext("2d");
    ctx.globalCompositeOperation = "destination-over";
    return ctx;
  }

  instantiateCircles(data) {
    const circles = [];

    // Instantiate circles
    for (let i = 0; i < data.length; i++) {
      const newCircle = new Circle();
      newCircle.color = data[i].color;
      newCircle.radius = data[i].size;
      newCircle.fixedX = data[i].fx;
      newCircle.fixedY = data[i].fy;
      newCircle.scale = 1.0;
      newCircle.setOriginalPosition(data[i].x, data[i].y);

      circles.push(newCircle);
    }

    return circles;
  }

  step(circles, ctx) {
    // Wiggle circles
    for (const i in circles) {
      this.wiggleCircle(circles[i], ctx);
    }
  }

  wiggleCircle(circle) {
    if (circle.isWiggling) {

      const tweenProperties = [
        {
          name: "scale",
          min: 0.93,
          max: 1
        }, {
          name: "x",
          min: circle.x - 12,
          max: circle.x + 12
        }, {
          name: "y",
          min: circle.y - 12,
          max: circle.y + 12
        }];

      this.wiggleProperties(circle, tweenProperties);
    }
  }

  wiggleProperties(obj, properties) {
    const duration = Math.random() * 4 + 1.5;
    const tweenOptions = {
      ease: Power1.easeInOut,
      onComplete: () => { this.wiggleProperties(obj, properties); }
    };

    for (const i in properties) {
      const property = properties[i];
      tweenOptions[property.name] = Math.random() * (property.max - property.min) + property.min;
    }

    TweenLite.to(obj, duration, tweenOptions);
  }

  draw(circles, ctx) {
    const canvasEl = ctx.canvas;

    // Clear canvas
    ctx.clearRect(0, 0, canvasEl.clientWidth, canvasEl.clientHeight);

    // Draw circles
    for (const i in circles) {
      this.drawCircle(circles[i], ctx);
    }
  }

  drawCircle(circle, ctx) {
    const rad = circle.calculateRadius();
    const pos = circle.calculateRelativePosition(ctx);

    ctx.beginPath();
    ctx.arc(pos.x, pos.y, rad, 0, 2 * Math.PI, false);
    ctx.fillStyle = circle.color;
    ctx.fill();
  }

  resizeCanvas(ctx) {
    const wrapperEl = document.getElementById("canvas__wrapper");

    ctx.canvas.height = wrapperEl.clientHeight;
    ctx.canvas.width = wrapperEl.clientWidth;
  }
}

new Canvas();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js