-
-
Save vlandham/73d584f1c9455d84abac to your computer and use it in GitHub Desktop.
2D Picking with canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment