<div id="wrapper">

  <div id="container"></div>
  <div id="output">> Interaction meta will output here.</div>
</div>
<div id="description">
  Flick the bubbles around.
</div>
* {
  box-sizing: border-box;
}

html,body {
  margin: 0;
  padding: 0;
  font-family: "Helvetica", Arial;
  height: 100%;
}

#wrapper {
  display: flex;
  flex-direction: column;
  height: 100%;
}

#container {
  display: flex;
  flex: 1;
  width: 100%;
}
#output {
  background-color: #3B3B3B;
  width: 100%;
  display: block;
  min-height: 30px;
  padding: 10px;
  font-size: 0.8em;
  color: white;
}

#description {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  color: #777;
  z-index: -1;
  height: 100%;
  width: 100%;
  font-size: 2rem;
  text-align: center;
}

var NUM_BUBBLES = 10;

var canvas = document.createElement('canvas');
var canvasPicker = document.createElement('canvas');
canvas.id = "main-canvas";
canvasPicker.id = "picker-canvas";
canvasPicker.style.visibility = "hidden";
canvasPicker.style.visibility = "hidden";
canvasPicker.style.position = 'fixed';

var bubbles = [];
var container = document.getElementById('container');
container.appendChild(canvas);
container.appendChild(canvasPicker);

var bBox = container.getBoundingClientRect();
canvas.width = canvasPicker.width = bBox.width;
canvas.height = canvasPicker.height = bBox.height;

window.onresize = function(){
  console.log('fire');
  resizeCanvas();
}

function resizeCanvas(){
  var container = document.getElementById('container');
  container.appendChild(canvas);
  container.appendChild(canvasPicker);

  var bBox = container.getBoundingClientRect();
  canvas.width = canvasPicker.width = bBox.width;
  canvas.height = canvasPicker.height = bBox.height;
}

resizeCanvas();
var canvasRegion = new ZingTouch.Region(document.getElementById('container'));
//SWIPING
canvasRegion.bind(canvas, 'swipe', function(e) {
  var weight = 1.5;
  
  setOutput([
    ['Gesture', 'Swipe'],
    ['velocity', Math.floor(e.detail.data[0].velocity) + "px/ms"],
    ['currentDirection', Math.floor(e.detail.data[0].currentDirection) + "°"]
  ]);

  var canvas = document.getElementById('main-canvas');
  var canvasRect = canvas.getBoundingClientRect();
  var x = e.detail.events[0].x - canvasRect.left;
  var y = e.detail.events[0].y - canvasRect.top;

  bubbles[lastIndex].x = (x < 0) ? 0 : (x > canvasRect.width) ? canvasRect.width : x;
  bubbles[lastIndex].y = (y < 0) ? 0 : (y > canvasRect.height) ? canvasRect.height : y;
  var theta = Math.sin((Math.PI / 180) * e.detail.data[0].currentDirection);
  bubbles[lastIndex].vy = -1 * (2 * (e.detail.data[0].velocity * Math.sin((Math.PI / 180) * e.detail.data[0].currentDirection)));
  bubbles[lastIndex].vx = 2 * (e.detail.data[0].velocity * Math.cos((Math.PI / 180) * e.detail.data[0].currentDirection));
});

//PANNING
var currentIndex = lastIndex = null;
var customPan = new ZingTouch.Pan({
  threshold: 1
});
var startPan = customPan.start;

customPan.start = function(inputs) {
  var canvas = document.getElementById('main-canvas');
  var canvasRect = canvas.getBoundingClientRect();

  var x = inputs[0].current.x - canvasRect.left;
  var y = inputs[0].current.y - canvasRect.top;
  currentIndex = getIndex(x, y);
  if (currentIndex !== null) {
    bubbles[currentIndex].stopped = true;
  }

  return startPan.call(this, inputs);
}
canvasRegion.bind(canvas, customPan, function(e) {
  setOutput([
    ['Gesture', 'Pan'],
    ['currentDirection', Math.floor(e.detail.data[0].currentDirection) + "°"],
    ['directionFromOrigin', Math.floor(e.detail.data[0].directionFromOrigin) + "°"],
    ['distanceFromOrigin', Math.floor(e.detail.data[0].distanceFromOrigin) + "px"]
  ]);
  
  var originalEvent = e.detail.events[0].originalEvent;
  var canvas = document.getElementById('main-canvas');
  var canvasRect = canvas.getBoundingClientRect();

  var x = e.detail.events[0].x - canvasRect.left;
  var y = e.detail.events[0].y - canvasRect.top;

  var rect = canvas.getBoundingClientRect();
  bubbles[currentIndex].x = (x < 0) ? 0 : (x > rect.width) ? rect.width : x;
  bubbles[currentIndex].y = (y < 0) ? 0 : (y > rect.height) ? rect.height : y;

  //Change velocity.
  bubbles[currentIndex].vy = -1 * Math.sin((Math.PI / 180) * e.detail.data[0].currentDirection);
  bubbles[currentIndex].vx = Math.cos((Math.PI / 180) * e.detail.data[0].currentDirection);
});

var endPan = customPan.end;
customPan.end = function(inputs) {
  bubbles[currentIndex].stopped = false;
  lastIndex = currentIndex;
  currentIndex = null;
  return endPan.call(this, inputs);
}

function getIndex(x, y) {
  x = Math.floor(x);
  y = Math.floor(y);
  var canvas = document.getElementById('picker-canvas');
  ctx = canvas.getContext('2d');
  var colors = ctx.getImageData(x, y, 1, 1).data;
  var str = "";
  for (var i = 0; i < colors.length - 1; i++) {
    str += colors[i];
  }
  return parseInt(str);
}

var Bubble = function() {
  this.x = getRandNum(0, canvas.width);
  this.y = getRandNum(0, canvas.height);
  this.vx = getRandNum(-1, 1, 2);
  this.vy = getRandNum(-1, 1, 2);
  this.radius = 40;
  this.minRadius = getRandNum(30, 50);
  this.maxRadius = 50;
  this.stopped = false;
  this.grow = true;
  this.color = 'rgba(' + getRandNum(0, 10) + ',' + getRandNum(0, 250) + ',' + getRandNum(100, 255) + ',' + 0.6 + ')';
  this.rate = getRandNum(0.1, 0.2, 1);
};

Bubble.prototype = {
  render: function(id) {
    var canvas = document.getElementById('main-canvas');
    var canvasPicker = document.getElementById('picker-canvas');
    ctx = canvas.getContext('2d');
    ctxPicker = canvasPicker.getContext('2d');
    drawOnCanvas(this, ctx, id, false);
    drawOnCanvas(this, ctxPicker, id, true);

    function drawOnCanvas(_this, context, id, picker) {
      id = id + "";
      var arr = id.split('');
      while (arr.length < 3) {
        arr.unshift("0");
      }
      if (picker) {
        var color = arr.join(',');
        context.beginPath();
        context.arc(_this.x, _this.y, _this.radius, 0, 2 * Math.PI);
        context.fillStyle = 'rgba(' + color + ',1)';
        context.strokeStyle = 'rgba(' + color + ',1)';
      } else {
        var color = arr.join(',');
        context.beginPath();
        context.arc(_this.x, _this.y, _this.radius, 0, 2 * Math.PI);
        context.fillStyle = _this.color;
        context.strokeStyle = (_this.stopped) ? 'rgba(0,0,0,0.5)' : _this.color;
      }

      context.fill();
      context.stroke();
    }

  },
  update: function() {
    //UPDATABLE
    if (this.stopped) {
      return;
    }
    //
    //                // //RADIUS
    //                if (this.grow) {
    //                    if (this.radius < this.maxRadius) {
    //                        this.radius = this.rate + this.radius;
    //                    } else {
    //                        this.grow = false;
    //                        this.radius = this.radius - this.rate;
    //                    }
    //                } else {
    //                    if (this.radius > this.minRadius) {
    //                        this.radius = this.radius - this.rate;
    //                    } else {
    //                        this.grow = true;
    //                        this.radius = this.rate + this.radius;
    //                    }
    //                }
    //                this.radius = parseFloat(this.radius);

    //MOVEMENT
    var canvas = document.getElementById('main-canvas');
    var canvasRect = canvas.getBoundingClientRect();
    this.x = this.x + (this.vx * 1);
    this.y = this.y + (this.vy * 1);

    //Change direction / hit a boundary
    if (this.x > canvasRect.width || this.x < 0) {
      if (this.x < 0) {
        this.x = 0;
      } else {
        this.x = canvasRect.width;
      }

      //Reduce velocity
      var currentDirectionX = (this.vx > 0) ? 1 : -1;
      this.vx = Math.abs(this.vx) * 0.60; //Reduce velocity
      this.vx = (this.vx < 1) ? 1 : this.vx;
      this.vx = (currentDirectionX * -1) * this.vx;
    }

    if (this.y > canvasRect.height || this.y < 0) {
      if (this.y < 0) {
        this.y = 0;
      } else {
        this.y = canvasRect.height;
      }

      var currentDirectionY = (this.vy > 0) ? 1 : -1;
      this.vy = Math.abs(this.vy) * 0.60; //Reduce velocity
      this.vy = (this.vy < 1) ? 1 : this.vy;
      this.vy = (currentDirectionY * -1) * this.vy;
    }
  }
}

for (var i = 0; i < NUM_BUBBLES; i++) {
  var bubble = new Bubble();
  bubbles.push(bubble);
  bubble.render(i);
}

window.requestAnimationFrame(eventLoop);

function eventLoop(timestamp) {
  window.requestAnimationFrame(eventLoop);
  var canvas = document.getElementById('main-canvas');
  var canvasPicker = document.getElementById('picker-canvas');
  ctx = canvas.getContext('2d');
  ctxPicker = canvasPicker.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctxPicker.clearRect(0, 0, canvasPicker.width, canvasPicker.height);

  for (var i = 0; i < bubbles.length; i++) {
    bubbles[i].update(i);
    bubbles[i].render(i);
  }
}

function getRandNum(min, max, decimals) {
  decimals = (decimals) ? decimals : 0;
  return parseFloat((Math.random() * (max - min + 1) + min).toFixed(decimals));
}

function setOutput(data){
  var outputStr = "> ";
  for (var i = 0; i < data.length; i++){
    outputStr += data[i][0] + ": " + data[i][1] + ((i===data.length-1) ? '' : ' , ');
  }
  var output = document.getElementById('output');
  output.innerHTML = outputStr;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/374756/zingtouch-1.0.0.min.js