/** * 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);