block by vlandham 73d584f1c9455d84abac

2D Picking with canvas

Full Screen

index.js

/**
 * This example uses a hidden canvas to demonstrate a technique for
 * simulating DOM click events while rendering to a canvas.
 *
 * The basic technique is to render your visual markers twice, the second
 * time on a hidden canvas where each marker gets a unique color. We can then
 * look up that color to get back to the data in question.
 *
 * Open your web console and click on the squares to see their original
 * indices in the data array.
 */

window.addEventListener('load', function(){

  var data = [];

  var stats = new Stats();
  stats.setMode( 0 ); // 0: fps, 1: ms, 2: mb

  // align top-left
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.left = '0px';
  stats.domElement.style.top = '0px';

  document.body.appendChild( stats.domElement );

  var controls = {count:100};

  var gui = new dat.GUI();
  var controller = gui.add(controls, 'count', 0, 40000).step(100);
  controller.onChange(function(value) {
    data = makeData(value);
  });

  var width = 960;
  var height = 500;

  var mouse = {};
  var overIndex = -1;

  var mainCanvas = document.createElement("canvas");

  mainCanvas.setAttribute('width',  width);
  mainCanvas.setAttribute('height', height);

  var container = document.querySelector("#container");
  container.appendChild(mainCanvas);

  // A map to lookup nodes by color used in the hidden canvas.
  var colToNode = {};

  /*
    Generate the data.
   */
  function makeData(count) {
    var data = [];
    for(var i = 0; i < count; i++) {
      var obj = {
        x: Math.random() * (width - 20),
        y: Math.random() * (height - 20),
        xVel: (Math.random() * 0.5) * (Math.random() < 0.5 ? -1 : 1),
        yVel: (Math.random() * 0.5) * (Math.random() < 0.5 ? -1 : 1),
        width: 15,
        height: 15,
        index: i
      };
      data.push(obj);
    }
    return data;
  }

  /*
    Updates the nodes on each frame to make them bounce around the screen.
   */
  function update(data) {
    var numElements = data.length;
    for(var i = 0; i < numElements; i++) {
      var node = data[i];
      node.x += node.xVel;
      node.y += node.yVel;

      if(node.x > width || node.x < 0) {
        node.xVel *= -1;
      }
      if(node.y > height || node.y < 0) {
        node.yVel *= -1;
      }
    }
  }


  /*
    Generates the next color in the sequence, going from 0,0,0 to 255,255,255.
   */
  var nextCol = 1;
  function genColor(){
    var ret = [];
    // via http://stackoverflow.com/a/15804183
    if(nextCol < 16777215){
      ret.push(nextCol & 0xff); // R
      ret.push((nextCol & 0xff00) >> 8); // G
      ret.push((nextCol & 0xff0000) >> 16); // B

      nextCol += 100; // This is exagerated for this example and would ordinarily be 1.
    }
    var col = "rgb(" + ret.join(',') + ")";
    return col;
  }

  /*
   * Returns true if a x/y point is over a given rectangle
   */
  function isOver(point, rect) {

    return (point.x) && (point.y) && (point.y > rect.y) && (point.y < (rect.y + rect.height)) && (point.x > rect.x) && (point.x < rect.x + rect.width);
  }

  function draw(data, canvas) {

    var ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, width, height);

    var overIndexSet = false;
    var numElements = data.length;

    for(var i = 0; i < numElements; i++) {
      var node = data[i];

      if(isOver(mouse, node)) {
        overIndex = i;
        overIndexSet = true;
        ctx.fillStyle = 'steelblue';
      } else if(node.renderCol) {
        ctx.fillStyle = node.renderCol;
      } else {
        ctx.fillStyle = 'DimGray';
      }

      // Draw the actual rectangle
      ctx.fillRect(node.x, node.y, node.width, node.height);
    }

    // if not over anything during this draw
    // clear overIndex
    if(!overIndexSet) {
      overIndex = -1;
    }
  }

  function getMousePos(canvas, evt) {
    var rect = canvas.getBoundingClientRect(), root = document.documentElement;

    // return relative mouse position
    var mouseX = evt.clientX - rect.left - root.scrollLeft;
    var mouseY = evt.clientY - rect.top - root.scrollTop;
    return {
      x: mouseX,
      y: mouseY
    };
  }

  function setMouse(e) {
    mouse = getMousePos(mainCanvas, e);
  }

  function handleClick(e) {
    console.log(overIndex);
    if (overIndex >= 0) {
      var node = data[overIndex];
      node.renderCol = 'orange';
    }
  }

  mainCanvas.addEventListener("mousemove", setMouse);
  mainCanvas.addEventListener("click", handleClick);

  // Generate the data and start the draw loop.

  data = makeData(100); // Increase this number to get more boxes
  function animate() {
    stats.begin();
    draw(data, mainCanvas);
    update(data);
    stats.end();
    window.requestAnimationFrame(animate);
  }
  window.requestAnimationFrame(animate);



}, false);

index.html

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>2D Picking with canvas</title>
  <meta name="description" content="">
  <meta name="author" content="Yannick Assogba">
  <script src="//rawgit.com/mrdoob/stats.js/master/build/stats.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.js"></script>

</head>

<body>
  <div id='container'>
  </div>

  <script src="index.js"></script>
</body>
</html>