Skip to content

Instantly share code, notes, and snippets.

@vlandham
Forked from tafsiri/index.html
Last active Aug 27, 2015
Embed
What would you like to do?
2D Picking with canvas
<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 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