How to recognize a click on a drawn rectangle on canvas - javascript

Say we drawed multiple rectangles in a html5 canvas:
context.fillStyle='black';
context.fillStroke='black';
context.beginPath();
for(var i=0; i<50; i++)
{
context.rect(i*20,i*20,w,h);
//this is just some random configuration for the rectangles, it doesn't really matter how they are positioned
}
context.closePath();
context.fill();
context.stroke();
How can I make it so it's recognizable when the user click on an individual rectangle and then, say, change it's color?
Is it possible or will I have to make a function that takes the mouse x and y coordinates and then check where it landed compared to the x and y coordinates of the rectangles, to finally find the one that "covers" the mouse coordinates?

IMO it would be better to keep track of the rectangles inside an array and just loop through them to see if it's inside. As stated Here:
When you draw to a canvas element, you are simply drawing a bitmap in
immediate mode.
The elements (shapes, lines, images) that are drawn have no
representation besides the pixels they use and their colour.[...]
Here comes the BUT: I found this Alternative

Related

What is the best way to programatically draw the border(s) of a group of squares?

I am building a tool which will ultimately leverage the Google Maps API v3 to build up an area on a map constructed of squares of a fixed edge length (e.g. 10 metres) on a fixed “grid” system (e.g., co-ordinates spaced out every 0.0001 latlong units starting at earth’s 0,0 point).
I have written code where users can click on an area on the map and the code draws an outline and fill of the square where it's found. Users can click on other adjacent locations to that square to build up a larger and larger “blocky” polygon, and can click on individual squares to delete them. I have tested all this myself in both HTML5 canvas/JavaScript as well as the Google Maps API.
Now I want to write code that removes any internal edges/vertices so that it only draws the outermost boundaries of this polygon so that it is actually drawn as one large polygon, rather than a collection of squares. Think of it this way: even though we know countries like Australia, USA etc., are comprised of a number of states, when we draw the border around the country we are not usually interested in the borders of all the states and can delete those lines in between and just draw the outer boundary. This is the same thing I want to accomplish, just using a grid of squares rather than complex polygons.
My current code is here:
https://jsfiddle.net/wyxcvmdf/14/
HTML:
<canvas id="myCanvas" width="500" height="250" style="border:1px solid #000000;"></canvas>
<!--etc.-->
JavaScript:
// don't forget to set load type in jsfiddle to no wrap in <body>
// define the global variable and some helper variables to make the code shorter
var gv = {};
gv.o = function(id) {
return document.getElementById(id)
};
gv.i = 'innerHTML';
// etc.
A couple of explanatory notes about my code:
• The “origin point” for every square is the vertex at the bottom left corner of that square. No particular reason for this.
• The “drawing direction” in terms of how HTML5 canvas draws the outline is counter-clockwise from the origin point. Again, no particular reason for this.
• You can’t “click” to add squares yet as it’s just a proof of concept, so you add squares by entering the x and y co-ordinates in the relevant text entry boxes
The use cases/tests required to prove my code which I have thought of are:
Square added to polygon with 1 duplicate vertex (working)
Square added to polygon with 2 and 3 duplicate vertices in all cases: adjacent edges, non-adjacent edges, non-sequential vertices (currently working for first case only)
Square added to polygon with 4 duplicate vertices in all cases: plugging a hole, plugging part of a hole, joining multiple polygons (currently working for first case only)
Square removed from polygon with 1 duplicate vertex in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square removed from polygon with 2 and 3 duplicate vertices in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square removed from polygon with 4 duplicate vertices in cases described above (not developed yet, but should effectively be “reverse” of addition code)
Square addition/removal on outside of polygon with multiple inner borders, i.e., holes (not developed yet, may be tricky)
Square addition/removal on inside of polygon with multiple inner borders, i.e., holes (not developed yet, may be tricky)
Note 1: My use of “squares”, “edge” etc., instead of "polygons", etc., is just for simplicity of explanation.
Note 2: I have performed quite a bit of research on similar problems and possible solutions but haven’t really found anything which will meet my needs. The research I’ve done is on:
Travelling Salesman Problem. However, this is not about optimising a path – it is about making sure a path is “drawable” and hence heading in one direction. Overlapping vertices are totally fine as long as the resulting shape looks like what a user would expect it to.
Convex hull algorithm. Not really applicable as the hull could be convex, concave or even non-contiguous! Also, I think that by simplifying to a grid system I have removed the problem of having many scattered vertices where you need to determine how far they are from a centre point, use trigonometry etc.
Concave hull solutions. This gets closer to solving my problem, however what I have seen is that there are many plug-ins for commercial tools (e.g. ArcGIS) to do this, but no generic code (irrespective of programming language) which covers all of my use cases.
Tile-based games. You would think that any tile-based game which requires drawing boundaries around tiles (e.g. A player’s territory in a real-time strategy game) would have solved this problem, but not from what I can see.
You say "draw" rather than calculate the outside vertices, so ...
You can use clipping plus compositing to "hollow out" your set of squares.
Assume you have determined that these squares are inside your desired boundary (either partially or fully inside):
var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];
An illustration of squares that are inside your desired boundary.
Then, to draw just the boundary of the set of squares, you can:
Stroke (not fill) each of your inside squares: context.rect
Restrict futher drawing to the stroked rects: context.clip
Cause all new drawing to erase existing pixels: context.globalCompositeOperation = 'destination-out'
Fill the entire canvas with a solid color: context.fillRect(0,0,canvas.width,canvas.height).
The trick: Stroking a rectangle actually draws a stroke half-inside & half-outside the rectangle, so step#4 will erase the inside of the set of rectangles but (importantly!) will leave the half outside stroke.
So you end up with this:
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var aInside=[ {x:60,y:60},{x:80,y:60},{x:40,y:60},{x:60,y:40},{x:60,y:80} ];
// stroke all inside squares
ctx.save();
ctx.beginPath();
for(var i=0;i<aInside.length;i++){
var s=aInside[i];
ctx.rect(s.x,s.y,20,20);
}
ctx.stroke();
// clip to cause all new drawing to be inside the stroked squares
ctx.clip();
// set compositing to use new drawings to "erase" existing drawings
ctx.globalCompositeOperation='destination-out';
// Fill (===erase!) the entire canvas
// Clipping causes only the clipping area to be erased
// so the inside of the rects set is "hollowed out"
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.restore();
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=150 height=150></canvas>
An Algorithmic Note: If you want a set of the surviving vertices rather than a drawing, you can modify the Marching Squares Algorithm to return only the inflection points. Those inflection points are the vertices of your outside boundary.
This method addresses only drawing/appearance - it does not produce any new polygons. But it allow you to use a collection of polygons (any shape, here rectangles) and merge them visually to produce a merged outline. I base this answer on one of my earlier answers, but modified and adopted to fit the scenario here:
Draw all the rectangles as solids
Re-draw them offset around all edges and corners extruded to the thickness you want
Redraw the original rectangles but with global composite mode set to destination-outand centered on top
There are a few steps, but it works pretty fast.
A couple of notes:
If you have an existing background it would be necessary to use an off-screen canvas as a temporary stage. Not shown here, though the steps would be the same except that you would do these steps on the off-screen context and at the end you would copy the content from the off-screen canvas on top of the existing content of your display canvas.
If you have a lot of rectangles it can be optimized by drawing each single rectangle to a separate off-screen canvas without redrawing anything else. Then you just use this off-screen canvas as a source when you do the extrusion process shown below (see link above for example, just replace image with off-screen canvas itself as source).
It can be further optimized by checking if a rectangle is embedded and if so remove it from the collection.
Demo
var ctx = c.getContext("2d"),
rw = 50, rh = 50, // some demo size
rectangles = []; // rectangle collection
function render(ctx) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "#a00";
ctx.globalCompositeOperation = "source-over"; // draw using standard mode3
// we will draw the same rects on top of each other eight times
// this will extrude the edge so we can in the next step punch a
// hole in the drawing and leave only the extrusion -
// offset array (x,y) pairs
var i, d = 2, // d = number of pixels to offset
offsets = [-d, -d, 0, -d, d, -d, d, 0, d, d, 0, d, -d, d, -d, 0];
for(i = 0; i < offsets.length; i += 2) {
ctx.setTransform(1,0,0,1, offsets[i], offsets[i+1]);
drawRects()
}
// punch hole in the center
ctx.setTransform(1,0,0,1,0,0); // reset transformatons
ctx.globalCompositeOperation = "destination-out"; // "erase" mode
drawRects(); // draw a final time, wo/ extrusion
function drawRects() {
ctx.beginPath();
rectangles.forEach(function(r) {
ctx.rect(r.x, r.y, r.w, r.h)
}); // loop through collection and draw
ctx.fill()
}
}
// demo stuff --
c.onclick = function(e) {
var r = this.getBoundingClientRect(), // for demo, get mouse position
x = e.clientX - r.left,
y = e.clientY - r.top;
// add rectangle to list
rectangles.push({ // generate a rect. from center
x: x - rw*0.5,
y: y - rh*0.5,
w: rw,
h: rh
});
render(ctx); // the key process
};
canvas {border:1px solid #000}
Click on the canvas below to place rectangles:<br>
<canvas width=600 height=600 id=c></canvas>

HTML5 Canvas - Mixing multiple translate() and scale() calls

I just wonder how do the Canvas transformations work. Lets say i have a canvas with a circle drawn somewhere inside of it, and i want to scale the circle, so its center point will not move.
So i thought about doing the following:
translate(-circle.x, -circle.y);
scale(factor,factor);
translate(circle.x,circle.y);
// Now, Draw the circle by calling arc() and fill()
Is it the right way to do it? I just don't understand whether the canvas was designed to remember the order that i call the transformations.
Thanks.
Yes, you are correct.
The canvas accumulates all transforms and applies them to any future drawing.
So if you scale 2X, your circle will be drawn at 2X…and(!) every draw after that will be 2X.
That’s where saving the context is useful.
If you want to scale your circle by 2X but then have every subsequent drawing be at normal 1X you can use this pattern.
// save the current 1X context
Context.save();
// move (translate) to where you want your circle’s center to be
Context.translate(50,50)
// scale the context
Context.scale(2,2);
// draw your circle
// note: since we’re already translated to your circles center, we draw at [0,0].
Context.arc(0,0,25,0,Math.PI*2,false);
// restore the context to it’s beginning state: 1X and not-translated
Context.restore();
After Context.restore, your translate and scale will not apply to further drawings.

HTML5/Javascript - How to get the coordinates of a shape/image on the canvas?

Say I drew a rectangle on the canvas. Surely there is some sort of built in method to get the XY coordinates, and dimensions of that rectangle? But after some googling I came up with nothing. And just to clarify, I am not talking about the coordinates of the canvas element itself, but rather a shape/image that is drawn unto the canvas.
Any help is appreciated.
If you're talking about a 2D canvas drawing, then the drawing maps 1:1 with screen coordinates, so it is just location of <canvas> + location of the drawing.
To clarify, drawing on a <canvas> basically just changes the pixels of the canvas - after you draw to it, you can't reference the drawn object the same way you can reference an html element.
Canvas is 2D table (Array) of numbers (= pixels = colors). When drawing into canvas, you are just editing this table. When you draw into canvas (= change numbers in table), what should be the coordinates of your adjustment?
If you are drawing rectangles only and you can define the coordinates for your rectangle, you must know your coordinates inside a program, because you have just drawn it.
If you want your image to be separated into some "objects" (shapes), you should use SVG.
Basically, you should be using canvas as a means to output graphics to the screen and the rest of your logic goes straight into the JavaScript that powers your game/application. The best way to go about making something like this is to create objects and assign properties to them; in its simplest form that can look like this:
function Player(x, y)
{
this.x = x;
this.y = y;
}
var examplePlayerObject = new Player(20, 20);
By extending this object via prototyping you can create multiple copies of an object that has the exact same functions; such as draw. Drawing the player in this instance could just be a red square that is 20px*20px.
Player.prototype.draw = function()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'red';
context.fillRect(this.x, this.y, 20, 20);
}
Then, you should have an update step with some means of clearing what is on the screen and redrawing the parts which have changed.
function animationStep()
{
examplePlayerObject.x++;
examplePlayerObject.y++;
examplePlayerObject.draw();
}
This animation step should run each frame; look into requestAnimationFrame for smooth animation. Paul Irish has a good shim for older browsers. Add in requestAnimationFrame(animationStep) at the end of that function and you will have a red square moving slowly across the screen. Good luck!

Canvas mouseover arc

I have a simply question - is there and option to detect the mouseover event on canvas arc? I want to make arc with radius 10, and when the mouseover "do something". I tried to do something like that:
context.arc(100,200,10,0,2*Math.PI);
canvas.addEventListener('mouseover', function(e) {
if(e.clientX == 100 && e.clientY == 200 ){
alert('omg');
};
});
...but it will show only when the mouse will be exactly on x=100, y=200 position (and I'm not ever sure if it will work)...
I don't want to use KinectJS. Is there any other way to do this?
I don't know how complex your scene is. Provided you have just that arc there on the canvas, you could use color picking (getPixel) on mouse over and compare the color against the arc color.
If the scene is more complex and you have multiple shapes with same colors, you could maintain another, hidden canvas in which you draw the shapes in specific colors (separate color per object). Then perform the color picking check against that canvas.

Is it possible to translate a shape in html5?

I wanted to know if Html5 supports shape translation in canvas..For instance I have a rectangle,is it possible to apply a transformation to it?
canvas = document.getElementById('Canvas');
context =canvas.getContext('2d');
context.rect(myRectangle.x,myRectangle.y,myRectangle.width,myRectangle.height);
There's a few different methods for animating and changing the position that you want to draw your thingy. Either way, if you're after an animation, you're going to need to clear your canvas and keep drawing - like a flip book if you will.
Choices for setting the newly drawn item include:
moveTo - to move to the new position of your thing
translate - to translate the centre point of the canvas and keep the drawing positions the same, but move the underlying coordinate system
.rect(newX, newY, height, width) - drawing the specific position
I mocked together a (contrived) example of using translate on a canvas - which will move the the animating box around the position of your cursor. It's done in a loop - and I'd suggest checking out Paul Irish's article on requestAnimFrame for better animation loops. Here's the example: http://jsbin.com/afofur/2/edit#preview
As the comments say in the previous answer - SVG maintains a object model, so you can reference objects on the page, canvas is a bitmap API (basically), and once the pixels are committed to the canvas, there's no reference to the method or shape behind the drawing, it's just pixels to the canvas API.
No, once it is drawn to the canvas you can't change it anymore, there is no in-memory representation of the shapes you draw on the canvas. However, you can transform the canvas before you draw the shape and reset transform (canvas.setTransform(1, 0, 0, 1, 0, 0)) after you've drawn the shape.
Edit
Remember that the canvas API doesn't keep track of which objects you draw. It just fills the pixels with a color where you ask it to draw a rectangle. If you want to make animations, you will have to keep track of which rectangle you drawn yourself (make an object with properties x, y, width, height). Then you will have to do the following in each animation step:
clear the canvas
update the objects for the new time frame
redraw the canvas
You can find a tutorial here.

Categories