How to select HTML5 canvas shapes? - javascript

I have a HTML5 canvas on which I have drawn several shapes.
What I want to happen, is that when the mouse is clicked on any shape the shape should get selected (at least it can tell what kind of shape is selected).
Thank you.

Try using an existing canvas library (or create your own) that has an event when a shape is being selected.
The example below uses the Kinetic JS library, and the example below is from HTML5 Canvas Region Events Example:
var triangle = new Kinetic.Shape(function(){
var context = this.getContext();
context.beginPath();
context.lineWidth = 4;
context.strokeStyle = "black";
context.fillStyle = "#00D2FF";
context.moveTo(120, 50);
context.lineTo(250, 80);
context.lineTo(150, 170);
context.closePath();
context.fill();
context.stroke();
});
triangle.on("mouseout", function(){
writeMessage(messageLayer, "Mouseout triangle");
});
triangle.on("mousemove", function(){
var mousePos = stage.getMousePosition();
var x = mousePos.x - 120;
var y = mousePos.y - 50;
writeMessage(messageLayer, "x: " + x + ", y: " + y);
});
shapesLayer.add(triangle);
var circle = new Kinetic.Shape(function(){
var canvas = this.getCanvas();
var context = this.getContext();
context.beginPath();
context.arc(380, canvas.height / 2, 70, 0, Math.PI * 2, true);
context.fillStyle = "red";
context.fill();
context.lineWidth = 4;
context.stroke();
});
circle.on("mouseover", function(){
writeMessage(messageLayer, "Mouseover circle");
});
circle.on("mouseout", function(){
writeMessage(messageLayer, "Mouseout circle");
});
circle.on("mousedown", function(){
writeMessage(messageLayer, "Mousedown circle");
});
circle.on("mouseup", function(){
writeMessage(messageLayer, "Mouseup circle");
});
shapesLayer.add(circle);
stage.add(shapesLayer);
stage.add(messageLayer);
In addition, I have included some mouse-in detection if the cursor is within the shape, without using any javascript libraries.
Rectangular-based mouse-in detection:
function isCursorWithinRectangle(x, y, width, height, mouseX, mouseY) {
if(mouseX > x && mouseX < x + width && mouseY > y && mouseY < y + height) {
return true;
}
return false;
}
Circle-based mouse-in detection:
function isCursorWithinCircle(x, y, r, mouseX, mouseY) {
var distSqr = Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2);
if(distSqr < r * r) {
return true;
}
return false;
}

There's a very simple way to select complex shapes with pixel precision that doesn't involve bounding rectangles or math calculations.
The idea is that you duplicate all of your shapes onto a hidden secondary canvas, where you assign each shape a unique color. When you perform a mouseover or click event on the original canvas, you save the mouse's (x, y) coordinates in relation to the visible canvas, and then you look-up the pixel color on your hidden canvas using those same coordinates. Because each shape has a unique color on the hidden canvas, that color corresponds to the exact shape that the user selected.
Note that this only supports up to roughly 16.7 million shapes because RGB only has 24 bits of color, but that should be more than enough.
Here's a simple example using D3 and Canvas: http://bl.ocks.org/syntagmatic/6645345

Canvas does not have an interface for the elements on it like the DOM. It is solely used for drawing.
You need to create your assets as objects and use a drawing loop to paint them. You then forget about the canvas element, you work with your objects, with their offsets, etc.

Related

Issues with circle task in Canvas

I have been given the following task, but I am getting errors that can be seen when the code snippet is run. I would like some help figuring out what exactly I am doing wrong.
Basically, I need to draw a circle, make it so that it moves and changes the direction/color when touching the walls of the screen.
Task: create a Circle class with the following properties:
x - the initial value of the coordinate x
y is the initial value of the y coordinate
radius - values ​​of width and height
color - fill color Describe the methods:
draw () - marks off on the screen an element that is described by the given properties
setColor (newColor) - Changes the fill color to newColor
move ({x = 0, y = 0}) - moves the captured object by the vector (x, y) - each time period (for example, 100 ms) changes (adds \ subtracts)
to the values ​​x and y, respectively. When a circle collides with any
edge of the screen it is necessary to realize its mirror reflection
(change the value of the corresponding coordinate of the vector on the
opposite of the value of the sign, and call this method with the new
vector) and generate the collision event, collision, which is captured
at the document level.Hang on this event a handler that will change
the color of the pouring of the circle into another (random) value.
Movement occurs until the stop method is called.
stop () - stops the circle movement
If the Escape button on the keyboard was pressed, the movement should stop.
I created a canvas and set the frame to move. I drew a circle and tried to move it using setInterval(), but it seems like I'm losing the context.
let c = document.getElementById("mycanvas");
let ctx = c.getContext("2d");
let xinc = 1;
let yinc = 1;
class Circle {
constructor(xpos, ypos, radius, color) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.color = color;
}
draw() {
ctx.beginPath();
ctx.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
}
move(xpos, ypos) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
this.draw();
xpos += xinc;
ypos += yinc;
console.log(xpos, ypos);
if ((this.xpos > c.width - this.radius) || (this.xpos < 0 + this.radius)) {
xinc = -xinc;
}
if ((this.ypos > c.height - this.radius) || (this.ypos < 0 + this.radius)) {
yinc = -yinc;
}
setInterval(this.move, 10);
//this.draw();
}
}
let circle = new Circle(200, 300, 50, "red");
circle.draw();
circle.move(200, 300);
<canvas id="mycanvas" width="1335" height="650" style="border: 1px solid"> </canvas>
I am just starting to learn events and DOMs, please help me correctly implement this task
You are passing this.move to setInterval with no context - just a function, with no this to call it in. You can pass in this.move.bind(this) to create a bound function. You can also do it once in the constructor: this.move = this.move.bind(this).
Also, the call to beginPath in move seems unnecessary.

How can I detect if a polygon(drawn in canvas) is clicked by me? [duplicate]

I've created a grid of several distorted rectangles made with Bezier curves. Each rectangle has its own color on the picture.
Let's say, I want to add hover effect for each of these rectangles, therefore I need to know its dimensions. Since I can fill or stroke the figure I assume that there is some way to get them, but I'm not sure.
Here is the example of the rectangles:
So the question is, is there some method in the canvas API with which I can achieve the desired effect?
Yes you can use isPointInPath(Path2D, x, y) method.
Note that if you don't use the Path2D object, you can also call it just with isPointInPath(x, y), but then it will check on the currently being drawn path (declared with beginPath()).
var ctx = canvas.getContext('2d');
var myPath = new Path2D();
myPath.bezierCurveTo(50, 100, 180, 10, 20, 10);
myPath.lineTo(50, 100);
function draw(hover) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = hover ? 'red' : 'green';
ctx.fill(myPath);
}
canvas.onmousemove = function(e) {
var x = e.clientX - canvas.offsetLeft,
y = e.clientY - canvas.offsetTop;
var hover = ctx.isPointInPath(myPath, x, y)
draw(hover)
};
draw();
<canvas id="canvas"></canvas>

Reveal image on canvas with draw/strokes/paint

I want to achive the following:
Draw a bg-image to the canvas (once or if needed repeatedly)
The image should not be visible at the beginning
While i "paint" shapes to the canvas the bg-image should get visible where the shapes were drawn
The parts of the image that will be revealed shall be "painted" (like with a brush) so i want to use strokes.
What i tried:
- Do not clear the canvas
- Paint rects to the canvas with globalCompositeOperation = 'destination-in'
This works, the rectangles reveal the image but i need strokes
If i use strokes they are ignored with 'destination-in' while i see them with normal globalCompositeOperation.
Is this intended that the strokes are ignored? Is there a workaround like somehow converting the stroke/shape to a bitmap? Or do i have have to use two canvas elements?
In OpenGL i would first draw the image with its rgb values and with a = 0 and then only "paint" the alpha in.
You can solve it by these steps:
Set the image as a pattern
Set the pattern as fillStyle or strokeStyle
When you now fill/stroke your shapes the image will be revealed. Just make sure the initial image fits the area you want to reveal.
Example showing the principle, you should be able to adopt this to your needs:
var ctx = canvas.getContext("2d"),
img = new Image,
radius = 40;
img.onload = setup;
img.src = "http://i.imgur.com/bnAEEXq.jpg";
function setup() {
// set image as pattern for fillStyle
ctx.fillStyle = ctx.createPattern(this, "no-repeat");
// for demo only, reveals image while mousing over canvas
canvas.onmousemove = function(e) {
var r = this.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, 2*Math.PI);
ctx.fill();
};
}
<canvas id=canvas width=900 height=600></canvas>
Hope this helps!
Alternative solution:
Put the image as a normal image on your website
add a canvas and use CSS positioning to place it right above the image
Fill the canvas with the color you use as the page background
have your paint tools erase the canvas when you draw. By the way, you can set context.globalCompositionOperation = 'destination-out' to turn all drawing operations into an eraser.
Here is an example. As you can see, the alpha properties of your tools are respected.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//prepare canvas
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, 120, 120);
//prepare a 30% opacity eraser
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
// make random strokes around cursor while mouse moves
canvas.onmousemove = function(e) {
var rect = this.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.beginPath();
ctx.moveTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.lineTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.stroke();
}
<span>Move your mouse:</span>
<div>
<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/120px-HTML5_logo_and_wordmark.svg.png' style='position:absolute'>
<canvas id='canvas' width=120 height=120 style='position:absolute'></canvas>
</div>

Rectangle selection of SVG elements (for Raphael)

I bumped into the following problem, hope someone will know how to help me:
I work with the JavaScript library Raphael. Now, what I want to do is, when I have many Raphael SVG elements, to simply select more elements with "rectangle selection", i.e. by dragging the mouse starting from the graph's background to create a selection rectangle (I hope I was clear enough), and move the elements which are in this rectangle.
For now, I've found something like this (someone posted it from a previous question of mine):
var paper = Raphael(0, 0, '100%', '100%');
var circle = paper.circle(75, 75, 50);
var rect = paper.rect(150, 150, 50, 50);
var set = paper.set();
set.push(circle, rect);
set.attr({
fill: 'red',
stroke: 0
});
var ox = 0;
var oy = 0;
var dragging = false;
set.mousedown(function(event) {
ox = event.screenX;
oy = event.screenY;
set.attr({
opacity: .5
});
dragging = true;
});
set.mousemove(function(event) {
if (dragging) {
set.translate(event.screenX - ox, event.screenY - oy);
ox = event.screenX;
oy = event.screenY;
}
});
set.mouseup(function(event) {
dragging = false;
set.attr({
opacity: 1
});
});
This code can be executed on jsfiddle. But, as you can see, this selects ALL the elements, by simply adding them to a Raphael set.
Now, I think that my problem will be solved by:
Making a rectangle selection
Adding the nodes which are in the rectangle to a Raphael set
Move only the selected items (i.e. move only the items which are in the Raphael set with set.mousemove)
My problem for now would be the first two issues.
Any ideas how to do this?
Thank you in advance!
Fun problem. You can do this by placing a rectangular "mat" the size of the canvas behind all of your other objects and attaching a drag event to it for selecting other elements. (Note this solution uses the newer version of Raphael, 2.1.0:
var paper = Raphael(0, 0, '100%', '100%');
//make an object in the background on which to attach drag events
var mat = paper.rect(0, 0, paper.width, paper.height).attr("fill", "#FFF");
var circle = paper.circle(75, 75, 50);
var rect = paper.rect(150, 150, 50, 50);
var set = paper.set();
set.push(circle, rect);
set.attr({
fill: 'red',
stroke: 0
});
//the box we're going to draw to track the selection
var box;
//set that will receive the selected items
var selections = paper.set();
Now, we add a drag event -- similar to the mouseover events but with three functions (see documentation), and draw a box to track the selection space:
//DRAG FUNCTIONS
//when mouse goes down over background, start drawing selection box
function dragstart (x, y, event) {
box = paper.rect(x, y, 0, 0).attr("stroke", "#9999FF");
}
// When mouse moves during drag, adjust box.
// If the drag is to the left or above original point,
// you have to translate the whole box and invert the dx
// or dy values since .rect() doesn't take negative width or height
function dragmove (dx, dy, x, y, event) {
var xoffset = 0,
yoffset = 0;
if (dx < 0) {
xoffset = dx;
dx = -1 * dx;
}
if (dy < 0) {
yoffset = dy;
dy = -1 * dy;
}
box.transform("T" + xoffset + "," + yoffset);
box.attr("width", dx);
box.attr("height", dy);
}
function dragend (event) {
//get the bounds of the selections
var bounds = box.getBBox();
box.remove();
reset();
console.log(bounds);
for (var c in set.items) {
// Here, we want to get the x,y vales of each object
// regardless of what sort of shape it is.
// But rect uses rx and ry, circle uses cx and cy, etc
// So we'll see if the bounding boxes intercept instead
var mybounds = set[c].getBBox();
//do bounding boxes overlap?
//is one of this object's x extremes between the selection's xe xtremes?
if (mybounds.x >= bounds.x && mybounds.x <= bounds.x2 || mybounds.x2 >= bounds.x && mybounds.x2 <= bounds.x2) {
//same for y
if (mybounds.y >= bounds.y && mybounds.y <= bounds.y2 || mybounds.y2 >= bounds.y && mybounds.y2 <= bounds.y2) {
selections.push(set[c]);
}
}
selections.attr("opacity", 0.5);
}
}
function reset () {
//empty selections and reset opacity;
selections = paper.set();
set.attr("opacity", 1);
}
mat.drag(dragmove, dragstart, dragend);
mat.click(function(e) {
reset();
});
Just like that, you have a new set (selections) that contains every object that was selected by the mouse drag. You can then apply your mouseover events from the original to that set.
Note that this will select circle objects if you nick the corner of their bounding box with your selection box, even if it doesn't overlap with the area of the circle. You could make a special case for circles if this is a problem.
jsFiddle

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Categories

Resources