<canvas id='canv'></canvas>
<!--
click && hold inside the box to add force && Mardi Gras Beads.
!-->
body {
  width: 100%;
  margin: 0;
  overflow: hidden;
  background: hsla(0, 0%, 0%, 1);
  background-repeat: no-repeat;
  background-attachment: fixed;
  background-image: radial-gradient(top, circle cover, hsla(243, 15%, 38%, .8) 20%, hsla(0, 0%, 5%, 1) 80%);
}

canvas {
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate( -50%, -50%);
  border: 3px solid hsla(0, 0%, 5%, 1);
  border-radius: 2%;
  background: hsla(0, 0%, 5%, 1);
  background-image: radial-gradient(top, circle cover, hsla(243, 16%, 28%, .8) 20%, hsla(0, 0%, 0%, 1) 80%);
  box-shadow: inset 1px 2px 3px 3px hsla(0, 0%, 0%, 1);
  cursor: pointer;
}
function motion() {
  var c;
  var $;
  var w;
  var h;
  var u = 0;
  var msdn = true;
  var ms = {
    x: 140,
    y: 5
  };

  var grav = 0.05;
  var rng = 16;
  var nrng = rng * rng;
  var den = 2.5;
  var frc = 1;
  var nfrc = 1;
  var pli = 0.1;
  var gnum = 18;
  var gs = 1 / (280 / gnum);

  var parts;
  var nparts;
  var nxt;
  var nnxt;
  var cnt;
  var _frc;
  var grd;

  this.set = function() {
    c = document.getElementById("canv");
    $ = c.getContext('2d');
    w = c.width = 280;
    h = c.height = 280;

    parts = []
    nparts = 0;
    nxt = [];
    nnxt = 0;
    grd = [];

    var i, j;
    for (i = 0; i < gnum; i++) {
      grd[i] = new Array(gnum);
      for (j = 0; j < gnum; j++) {
        grd[i][j] = new Grid();
      }
    }
    cnt = 0;

    c.addEventListener('mousemove', function(e) {
      ms.x = e.layerX || e.offsetX;
      ms.y = e.layerY || e.offsetY;
    }, false);

    c.addEventListener('touchmove', function(e) {
      ms.x = e.touches[0].layerX || e.offsetX;
      ms.y = e.touches[0].layerY || e.offsetY;
      e.preventDefault();
    }, false);

    c.addEventListener('mousedown', function(e) {
      e.preventDefault();
      msdn = true;
    }, false);
    c.addEventListener('touchdown', function(e) {
      e.preventDefault();
      msdn = true;
    }, false);

    c.addEventListener('mouseup', function(e) {
      e.preventDefault();
      msdn = false;
    }, false);
    c.addEventListener('touchend', function(e) {
      e.preventDefault();
      msdn = false;
    }, false);
    c.addEventListener('mouseout', function(e) {
      e.preventDefault();
      msdn = false;
      clear();
    }, false);

    run();
  }

  function run() {
    window.requestAnimationFrame(run);
    upd();
  }

  var upd = function() {
    clear();
    if (msdn) {
      fall();
    }
    mv();
  }

  var fall = function() {
    var i;
    for (i = -4; i <= 4; i++) {
      parts[nparts++] = new P(ms.x + i * 10, ms.y,
        Math.floor(cnt / 10 % 5));
      parts[nparts - 1].vy = 5;

      if (nparts >= 1000) {
        parts.shift();
        nparts--;
      }
    }
  }
  var mv = function() {
    cnt++;

    gupd();
    fnxt();
    calc();
    var i, p;

    for (i = 0; i < nparts; i++) {
      p = parts[i];
      mvp(p);
      $.fillStyle = 'hsla(' + i + ',95%,50%,1)';
      $.fillRect(p.x - 1, p.y - 1, 3, 3);
    }
  }

  var gupd = function() {
    var p;
    var i, j;

    for (i = 0; i < gnum; i++) {
      for (j = 0; j < gnum; j++) {
        grd[i][j].parts.length = 0;
        grd[i][j].nparts = 0;
      }
    }
    for (i = 0; i < nparts; i++) {
      p = parts[i];
      p.fx = 0;
      p.fy = 0;
      p.den = 0;
      p.nden = 0;
      p.gx = Math.floor(p.x * gs);
      p.gy = Math.floor(p.y * gs);

      if (p.gx < 0) {
        p.gx = 0;
      }
      if (p.gy < 0) {
        p.gy = 0;
      }
      if (p.gx > gnum - 1) {
        p.gx = gnum - 1;
      }
      if (p.gy > gnum - 1) {
        p.gy = gnum - 1;
      }
    }
  }
  var fnxt = function() {
    var i;
    var p;
    nnxt = 0;

    for (i = 0; i < nparts; i++) {
      p = parts[i];
      var minX = p.gx != 0;
      var maxX = p.gx != (gnum - 1);
      var minY = p.gy != 0;
      var maxY = p.gy != (gnum - 1);
      locnxt(p, grd[p.gx][p.gy]);

      if (minX) {
        locnxt(p, grd[p.gx - 1][p.gy]);
      }
      if (maxX) {
        locnxt(p, grd[p.gx + 1][p.gy]);
      }
      if (minY) {
        locnxt(p, grd[p.gx][p.gy - 1]);
      }
      if (maxY) {
        locnxt(p, grd[p.gx][p.gy + 1]);
      }
      if (minX && minY) {
        locnxt(p, grd[p.gx - 1][p.gy - 1]);
      }
      if (minX && maxY) {
        locnxt(p, grd[p.gx - 1][p.gy + 1]);
      }
      if (maxX && minY) {
        locnxt(p, grd[p.gx + 1][p.gy - 1]);
      }
      if (maxX && maxY) {
        locnxt(p, grd[p.gx + 1][p.gy + 1]);
      }
      grd[p.gx][p.gy].add(p);
    }
  }

  var locnxt = function(pi, g) {
    var j;

    var jarr, dist;
    for (j = 0; j < g.nparts; j++) {

      jarr = g.parts[j];
      dist = (pi.x - jarr.x) * (pi.x - jarr.x) +
        (pi.y - jarr.y) * (pi.y - jarr.y);

      if (dist < nrng) {
        if (nxt.length == nnxt) {
          nxt[nnxt] = new Nxt();
        }
        nxt[nnxt++].setP(pi, jarr);
      }
    }
  }

  var calc = function() {
    var i;
    for (i = 0; i < nnxt; i++) {
      nxt[i].calc();
    }
  }
  var mvp = function(p) {
    p.vy += grav;

    if (p.den > 0) {
      p.vx += p.fx / (p.den * 0.9 + 0.1);
      p.vy += p.fy / (p.den * 0.9 + 0.1);
    }
    p.x += p.vx;
    p.y += p.vy;

    if (p.x < 5) {
      p.vx += (5 - p.x) * 0.5 - p.vx * 0.5;
    }
    if (p.x > 275) {
      p.vx += (275 - p.x) * 0.5 - p.vx * 0.5;
    }
    if (p.y < 5) {
      p.vy += (5 - p.y) * 0.5 - p.vy * 0.5;
    }
    if (p.y > 275) {
      p.vy += (275 - p.y) * 0.5 - p.vy * 0.5;
    }
  }

  var clear = function() {
    c.width = c.width
  }
}

function Nxt() {
  this.p1;
  this.p2;
  this.dist;
  this.nx;
  this.ny;
  this.wgt;
  this.rng = 16;
  this.frc = 1;
  this.nfrc = 1;
  this.den = 2.5;
  this.pli = 0.1;

  this.setP = function(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
    this.nx = p1.x - p2.x;
    this.ny = p1.y - p2.y;
    this.dist = Math.sqrt(this.nx * this.nx + this.ny * this.ny);
    this.wgt = 1 - this.dist / this.rng;
    var den = this.wgt * this.wgt;

    p1.den += den;
    p2.den += den;

    den *= this.wgt * this.nfrc;

    p1.nden += den;
    p2.nden += den;

    var invd = 1 / this.dist;
    this.nx *= invd;
    this.ny *= invd;
  }

  this.calc = function() {
    var p;
    var p1 = this.p1;
    var p2 = this.p2;

    if (this.p1.sort != this.p2.sort) {
      p = (p1.den + p2.den - this.den * 1.5) * this.frc;
    } else {
      p = (p1.den + p2.den - this.den * 2) * this.frc;
    }

    var pn = (p1.nden + p2.nden) * this.nfrc;
    var pwgt = this.wgt * (p + this.wgt * pn);
    var pliwgt = this.wgt * this.pli;
    var fx = this.nx * pwgt;
    var fy = this.ny * pwgt;

    fx += (p2.vx - p1.vx) * pliwgt;
    fy += (p2.vy - p1.vy) * pliwgt;

    p1.fx += fx;
    p1.fy += fy;

    p2.fx -= fx;
    p2.fy -= fy;
  }
}

function P(x, y, sort) {
  var u = 0;
  this.x = x;
  this.y = y;

  this.gx;
  this.gy;
  this.vx = 0;
  this.vy = 0;
  this.fx = 0;
  this.fy = 0;

  this.den;
  this.nden;

  this.sort = sort;
  this.grav = 0.05;
}

function Grid() {
  this.parts = new Array();
  this.nparts = 0;
  this.add = function(p) {
    this.parts[this.nparts++] = p;
  }
}
var mot = new motion();
mot.set();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.