/** * 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 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 width = 960; var height = 500; var mainCanvas = document.createElement("canvas"); var hiddenCanvas = document.createElement("canvas"); mainCanvas.setAttribute('width', width); mainCanvas.setAttribute('height', height); hiddenCanvas.setAttribute('width', width); hiddenCanvas.setAttribute('height', height); var container = document.querySelector("#container"); container.appendChild(mainCanvas); // container.appendChild(hiddenCanvas); // Include this to see the hidden canvas. // A map to lookup nodes by color used in the hidden canvas. var colToNode = {}; /* Generate the data. */ var data = []; function makeData(count) { 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; } var controls = {count:100}; var gui = new dat.GUI(); var controller = gui.add(controls, 'count', 0, 20000).step(100); controller.onChange(function(value) { data = makeData(value); }); /* 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; } function draw(data, canvas, hidden) { var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, width, height); var numElements = data.length; for(var i = 0; i < numElements; i++) { var node = data[i]; if(node.renderCol) { // Render clicked nodes in the color of their corresponding node // on the hidden canvas. ctx.fillStyle = node.renderCol; } else { ctx.fillStyle = 'DimGray'; } if(hidden) { if(node.__pickColor === undefined) { // If we have never drawn the node to the hidden canvas get a new // color for it and put it in the map. node.__pickColor = genColor(); colToNode[node.__pickColor] = node; } // On the hidden canvas each rectangle gets a unique color. ctx.fillStyle = node.__pickColor; } // Draw the actual rectangle ctx.fillRect(node.x, node.y, node.width, node.height); } } // Listen for clicks on the main canvas mainCanvas.addEventListener("click", function(e){ draw(data, hiddenCanvas, true); var mouseX = e.layerX; var mouseY = e.layerY; // Get the corresponding pixel color on the hidden canvas // and look up the node in our map. var ctx = hiddenCanvas.getContext("2d"); var col = ctx.getImageData(mouseX, mouseY, 1, 1).data; var colString = "rgb(" + col[0] + "," + col[1] + ","+ col[2] + ")"; var node = colToNode[colString]; if(node) { node.renderCol = node.__pickColor; console.log("Clicked on node with index:", node.index, node); } }); // 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); // draw(data, hiddenCanvas, true); update(data); stats.end(); window.requestAnimationFrame(animate); } window.requestAnimationFrame(animate); }, false);