I have drawn a circle with canvas using following code.
<div class="abc"><canvas id="myCanvas">HTML5 canvas tag.</canvas></div>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100,75,50,-0.1*Math.PI,1.7*Math.PI);
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke();
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
ctx2.beginPath();
ctx2.arc(100,75,50,1.7*Math.PI,-0.1*Math.PI);
ctx2.lineWidth = 15;
ctx2.strokeStyle = "#a9a9a9";
ctx2.stroke();
</script>
Here is the result on the web browser.
Now I need to call two different javascript function when the user clicks on red part and gray part of the circle.
On click of the red part it should call function1 and on click of gray part, it should call function2.
I've tried a lot but didn't succeed. I need some expert's help to implement it.
Letting the browser do (most of) the work
On click of the red part it should call function1 and on click of gray part, it should call function2.
You can use reusable path objects to store the different paths you want to test against and then use isPointInPath() with path and coordinates as arguments.
By checking each path for a hit you can assign a different call based on for example index.
And there is no need to do this:
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
This will simply reference the same context as a canvas can only have one - if requested more than once the same will be given (or null if different type).
How to use multiple click event on a canvas
You can share the click handler to do what you want as shown below -
For modern browsers you can use both Path2D objects to store path information you want to use later (I'll address older browsers in the second example).
Example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var p1 = new Path2D();
var p2 = new Path2D();
var paths = [p1, p2]; // store paths in array for check later
// store arc parts to respective path objects
p1.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
p2.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
// render the two path objects using a common context, but different style
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke(p1);
ctx.strokeStyle = "#a9a9a9";
ctx.stroke(p2);
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// iterate through path array to test each path for hits
for(var i = 0; i < paths.length; i++) {
if (ctx.isPointInStroke(paths[i], x, y)) { // check which path
console.log("Path " + (i+1) + " clicked");
break;
}
}
};
<canvas id="myCanvas"></canvas>
Browser compatibility
Older browsers however, won't support the isPointInStroke(), such as IE11.
For this scenario you can use simple math to figure out if the click is within the radius of the arcs:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 15;
ctx.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
ctx.strokeStyle = "#c32020";
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
ctx.strokeStyle = "#a9a9a9";
ctx.stroke();
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top,
diffX = 100 - x,
diffY = 75 - y,
len = diffX*diffX + diffY*diffY, // we don't need to square-root it:
r1 = 43*43, // it's faster to scale up radius (50-50% of linewidth)
r2 = 57*57; // radius + 50% of linewidth
// are we on the edge of the circle?
if (len >= r1 && len <= r2) {
// now find angle to see if we're in red or grey area
var angle = Math.atan2(diffY, diffX);
if (angle > 0.7 * Math.PI && angle < 0.9 * Math.PI) {
console.log("Grey part");
}
else {
console.log("Red part");
}
}
};
<canvas id="myCanvas"></canvas>
Note that a special case in the latter example, i.e. when the arc crosses the 0/360° point, you will want to split the checks [0, angle> and [angle, 360>.
You will need to add a click event to the canvas element and calculate if the red or gray is clicked.
Here's some code to get you started, but you'll still have to figure out what coordinates your circle area has.
var elem = document.getElementById('myCanvas'),
elemLeft = elem.offsetLeft,
elemTop = elem.offsetTop;
// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
var x = event.pageX,
y = event.pageY;
// use x and y to determine if the colored regions are clicked and execute code accordingly
}, false);
Code gotten from here: How do I add a simple onClick event handler to a canvas element?
Related
I am creating a game using the HTML5 Canvas element, and as one of the visual effects I would like to create a glow (like a light) effect. Previously for glow effects I found solutions involving creating shadows of shapes, but these require a solid shape or object to cast the shadow. What I am looking for is a way to create something like an ambient light glow with a source location but no object at the position.
Something I have thought of was to define a centerpoint x and y and create hundreds of concentric circles, each 1px larger than the last and each with a very low opacity, so that together they create a solid center and a transparent edge. However, this is very computationally heavy and does not seem elegant at all, as the resulting glow looks awkward.
While this is all that I am asking of and I would be more than happy to stop here, bonus points if your solution is A) computationally light, B) modifiable to create a focused direction of light, or even better, C) if there was a way to create an "inverted" light system in which the entire screen is darkened by a mask and the shade is lifted where there is light.
I have done several searches, but none have turned up any particularly illuminating results.
So I'm not quite sure what you want, but I hope the following snippet will help.
Instead of creating a lot of concentric circles, create one radialGradient.
Then you can combine this radial gradient with some blending, and even filters to modify the effect as you wish.
var img = new Image();
img.onload = init;
img.src = "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg";
var ctx = c.getContext('2d');
var gradCtx = c.cloneNode().getContext('2d');
var w, h;
var ratio;
function init() {
w = c.width = gradCtx.canvas.width = img.width;
h = c.height = gradCtx.canvas.height = img.height;
draw(w / 2, h / 2)
updateGradient();
c.onmousemove = throttle(handleMouseMove);
}
function updateGradient() {
var grad = gradCtx.createRadialGradient(w / 2, h / 2, w / 8, w / 2, h / 2, 0);
grad.addColorStop(0, 'transparent');
grad.addColorStop(1, 'white');
gradCtx.fillStyle = grad;
gradCtx.filter = "blur(5px)";
gradCtx.fillRect(0, 0, w, h);
}
function handleMouseMove(evt) {
var rect = c.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
draw(x, y);
}
function draw(x, y) {
ctx.clearRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(gradCtx.canvas, x - w / 2, y - h / 2);
ctx.globalCompositeOperation = 'lighten';
ctx.fillRect(0, 0, w, h);
}
function throttle(callback) {
var active = false; // a simple flag
var evt; // to keep track of the last event
var handler = function() { // fired only when screen has refreshed
active = false; // release our flag
callback(evt);
}
return function handleEvent(e) { // the actual event handler
evt = e; // save our event at each call
if (!active) { // only if we weren't already doing it
active = true; // raise the flag
requestAnimationFrame(handler); // wait for next screen refresh
};
}
}
<canvas id="c"></canvas>
I have a canvas on which user can draw point on click on any part of it. As one know we must give the user the possibility to undo an action that he as done. This is where I'am stuck, how can I program the codes to allow the user to remove the point on double click on the the point he wants to remove ?
<canvas id="canvas" width="160" height="160" style="cursor:crosshair;"></canvas>
1- Codes to draw the canvas and load an image in it
var canvasOjo1 = document.getElementById('canvas'),
context1 = canvasOjo1.getContext('2d');
ojo1();
function ojo1()
{
base_image1 = new Image();
base_image1.src = 'https://www.admedicall.com.do/admedicall_v1//assets/img/patients-pictures/620236447.jpg';
base_image1.onload = function(){
context1.drawImage(base_image1, 0, 0);
}
}
2- Codes to draw the points
$("#canvas").click(function(e){
getPosition(e);
});
var pointSize = 3;
function getPosition(event){
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
drawCoordinates(x,y);
}
function drawCoordinates(x,y){
var ctx = document.getElementById("canvas").getContext("2d");
ctx.fillStyle = "#ff2626"; // Red color
ctx.beginPath();
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true);
ctx.fill();
}
My fiddle :http://jsfiddle.net/xpvt214o/834918/
By hover the mouse over the image we see a cross to create the point.
How can i remove a point if i want to after create it on double click ?
Thank you in advance.
Please read first this answer how to differentiate single click event and double click event because this is a tricky thing.
For the sake of clarity I've simplified your code by removing irrelevant things.
Also please read the comments of my code.
let pointSize = 3;
var points = [];
var timeout = 300;
var clicks = 0;
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = 160);
let ch = (canvas.height = 160);
function getPosition(event) {
var rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
function drawCoordinates(point, r) {
ctx.fillStyle = "#ff2626"; // Red color
ctx.beginPath();
ctx.arc(point.x, point.y, r, 0, Math.PI * 2, true);
ctx.fill();
}
canvas.addEventListener("click", function(e) {
clicks++;
var m = getPosition(e);
// this point won't be added to the points array
// it's here only to mark the point on click since otherwise it will appear with a delay equal to the timeout
drawCoordinates(m, pointSize);
if (clicks == 1) {
setTimeout(function() {
if (clicks == 1) {
// on click add a new point to the points array
points.push(m);
} else { // on double click
// 1. check if point in path
for (let i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, pointSize, 0, Math.PI * 2, true);
if (ctx.isPointInPath(m.x, m.y)) {
points.splice(i, 1); // remove the point from the array
break;// if a point is found and removed, break the loop. No need to check any further.
}
}
//clear the canvas
ctx.clearRect(0, 0, cw, ch);
}
points.map(p => {
drawCoordinates(p, pointSize);
});
clicks = 0;
}, timeout);
}
});
body {
background: #20262E;
padding: 20px;
}
<canvas id="canvas" style="cursor:crosshair;border:1px solid white"></canvas>
I drawn a circle in canvas and put an image near the border. Now I have absolutely no idea..I want to drag the image around the circle but the top of the arrow image should always be on the border.
For Example: I drag the arrow from the top to the left at nine o'clock. Now the arrow image needs to be rotated 90 degrees.
http://jsfiddle.net/L5twk3ak/1/
canvas = document.getElementById('test');
var context = canvas.getContext("2d");
var points = [];
var radius = 55;
imageBG = new Image();
imageBG.onload = function() {context.drawImage(imageBG, 148, 100, 15, 15);};
imageBG.src = 'https://www.nanamee.com/upload/images/5945/5945_p.jpg';
for(var degree = 0; degree < 360; degree++)
{
var radians = degree * Math.PI / 179;
var x = 150 + radius * Math.cos(radians);
var y = 150 + radius * Math.sin(radians);
points.push({x : x, y : y});
}
context.beginPath();
context.moveTo(points[0].x + 4, points[0].y + 4)
for(var i = 1; i < points.length; i++)
{
var pt = points[i];
context.lineTo(pt.x + 4, pt.y + 4);
}
context.strokeStyle = "black";
context.lineWidth = 1;
context.stroke();
context.closePath();
<canvas id="test" width="400" height="400">Your browser does not support the HTML5 canvas tag.</canvas>
You need to :
Draw your Arc as we're supposed to (unless you have better plans with lineTo() )
calculate the mouse position inside the canvas - on mousemove.
calculate the resultant degree depending on Mouse Position vs. the Arc center.
cache your image for reuse
create draw functions (one for the Arc, the other for drawing the Image after translating the canvas context). That way on (click+drag) mousemove you can simply reuse them to draw your objects into Canvas.
I'll not show you how to implement the click+drag cause it's pretty trivial: you simply need to apply your draw functions if both CLICK+MOUSEMOVE are registered.
Here's the interesting calculations part:
var canvas = document.getElementById('test'); // Store in variable!
var context = canvas.getContext("2d");
var circle = {rad: 55, x:100, y:100}; // Object for ease of use
var img = {src:'//placehold.it/13x13/000', x:0 ,y:0, w:13, h:13};
var arrowImg; // Store for later Image reference
function drawArc(){
context.beginPath();
context.arc(circle.x, circle.y, circle.rad, 0, Math.PI*2, true);
context.strokeStyle = "#000";
context.lineWidth = 1;
context.stroke();
context.closePath();
}
function drawImg( deg ){
context.save(); // save before we mess with ctx translations
context.translate(circle.y, circle.x); // temporarily translate the ctx
// to the Arc center coordinates.
context.rotate(deg*Math.PI/180); // we need Radians so deg*Math.PI/180
context.drawImage(arrowImg, circle.rad-img.w, -img.h/2);
context.restore(); // restore to default
}
function calcDeg(e){ // Retrieve degree from mouse position vs. arc center
var mPos = {
x : e.pageX-canvas.offsetLeft-circle.x,
y : e.pageY-canvas.offsetTop-circle.y
};
var getAtan = Math.atan2(mPos.y, mPos.x);
return getAtan*180/Math.PI;
}
drawArc(); // Draw the ARc
arrowImg = new Image(); // Create Image Obj
arrowImg.onload = function(){ drawImg(-90) }; // onload draw the Image
arrowImg.src = img.src;
canvas.addEventListener("mousemove", function(evt){
canvas.width = canvas.width; // clear the canvas
drawArc(); // Draw Arc
drawImg( calcDeg(evt) ); // Draw Image at the calculated degree
}, false);
canvas{background:#eee;}
<canvas id="test" width="400" height="400">Your browser does not support the HTML5 canvas tag.</canvas>
Not clear? Goog, than ask
In a canvas I created a 2d context. In that context... with a function... I'm able to create some 'circle objects'. Now, what I want, is to get the ImageData of a single circle object instead of the image data of the whole context.
In the code below, you can see my wish commented out.
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var circle = function (X,Y) {
var that = this;
that.X = X;
that.Y = Y;
that.clicked = function(e) {
//
//
//!!!!!!!!!!!!!!
// Code below works fine, on context level
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
//
// Code below is at the level of the circle, that's what I want, but isn't working
imgData = that.getImageData(e.pageX, e.pageY, 1, 1);
//!!!!!!!!!!!!!!
//
//
alert(imgData.data[3]);
}
that.draw = function () {
ctx.save();
ctx.translate(that.X, that.Y);
ctx.fillStyle = '#33cc33';
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
var circles = new Array();
circles.push(new circle(50,50));
document.addEventListener('click',function() {
circles.forEach(function(circ,index){
circ.clicked();
});
})
So, how do I get the image data on specific objects?
edit:
I understand that I need to draw the circle first, I do that later in my code, but what if I've got a background rect in the context, when I click next to the circle, it will get the imageData of the background rect, when I want to return the 0 value of the alpha rgba.
To this you need to log all your drawings as a "shadow canvas". The most common way is to create shape objects and store them in for example an array:
Draw the shape on canvas
Log its type, position, dimension, colors and orientation and store as an object and push that object to the array
When you need to get an isolated shape or object as an image:
Get mouse position (if you want to click on the object to select it)
Iterate the array of objects to see which object is "hit"
Create a temporary canvas of the dimension of that shape
Draw in the shape into the temporary canvas
Extract the data as an image (ctx.getImageData(x, y, w, h) or canvas.toDataURL())
When you need to resize your canvas you simply iterate all the objects and redraw them. You can even serialize your data for storage using this method.
An example of an object can be:
function Rectangle(x, y, w, h, fill, stroke) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.fill = fill;
this.stroke = stroke;
}
You can extend this object to render it self to canvas as well as giving you a bitmap of itself isolated from the other shapes. Add this to the above code:
Rectangle.prototype.render = function(ctx) {
if (this.fill) { /// fill only if fill is defined
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
if (this.stroke) { /// stroke only if stroke is defined
ctx.strokeStyle = this.stroke;
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
Rectangle.prototype.toBitmap = function() {
var tcanvas = document.createElement('canvas'), /// create temp canvas
tctx = tcanvas.getContext('2d'); /// temp context
tcanvas.width = this.width; /// set width = shape width
tcanvas.height = this.height;
tctx.translate(-this.x, -this.y); /// make sure shape is drawn at origin
this.render(tcxt); /// render itself to temp context
return tcanvas.toDataURL(); /// return image (or use getImageData)
}
You simply draw your shapes, create the object based on the positions etc:
var rect = new Rectangle(x, y, w, h, fillColor, strokeColor);
myShapeArray.push(rect);
When you need to render the shapes:
for(var i = 0, shape; shape = myShapeArray[i++];)
shape.render(ctx);
And when you need to get its bitmap (you retrieved its index in advance with the mouse click):
var image = myShapeArray[index].toBitmap();
And of course: you can make similar objects for circles, lines etc.
Hope this helps!
Remember that Canvas is a bitmap graphics tool. Anything you draw into a single context becomes part and parcel of the same object. You can't get separate image data for each "object" you used to draw on that canvas... it's painted ... flattened ... into those pixel positions for that bitmap as soon as you hit draw().
The only way you could do something like what you are looking for would be to create separate canvas contexts that you overlay on top of each other. This would be better handled by utilizing a library such as KineticJS (http://www.html5canvastutorials.com/kineticjs/html5-canvas-events-tutorials-introduction-with-kineticjs/). The only other option would be to use an object oriented drawing tool such as SVG, (through Raphael.js, for example: http://raphaeljs.com) which does preserve separate objects in the the graphics space.
For reference about getImageData, see http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/
You can use trigonometry instead of trying to locate your colors with getImageData.
For example, if you have a circle defined like this:
var centerX=150;
var centerY=150;
var radius=20;
var circleColor="red";
Then you can test if any x,y is inside that circle like this:
// returns true if x,y is inside the red circle
isXYinCircle(140,140,centerX,centerY,radius);
function isXYinCircle(x,y,cx,cy,r){
var dx=x-cx;
var dy=y-cy;
return(dx*dx+dy*dy<=r*r);
}
If the x,y is inside that red circle then you know the color at x,y is "red"
If you have multiple overlapping circles you can test each circle in increasing z-index order. The last circle that reports x,y inside will be the color at x,y.
It is because that is not a CanvasGraphicsContext. Try:
that.draw();
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
At first, I create my 2 canvas elements. 1 to display, 1 to calculate the pixeldata.
var c = document.getElementById('canvas');
var c2 = document.getElementById('canvas2');
var ctx = c.getContext('2d');
var ctx2 = c2.getContext('2d');
var width = window.innerWidth,
height = window.innerHeight;
c.width = ctx.width = c2.width = ctx2.width = width;
c.height = ctx.height = c2.height = ctx2.height = height;
Than I make my function to create an image
function Afbeelding(src, X, Y, W, H) {
var that = this;
that.X = X;
that.Y = Y;
that.W = W;
that.H = H;
that.onClick = function () { };
that.image = new Image(that.W, that.H);
that.image.src = src;
that.draw = function (context) {
context = (typeof context != 'undefined') ? context : ctx;
context.save();
context.translate(that.X, that.Y);
context.drawImage(that.image, 0, 0, that.W, that.H);
context.restore();
}
When a document.click event is fired, the next function (inside the Afbeelding function) will be called:
that.clicked = function (e) {
if ((e.pageX > that.X - (that.W / 2) && e.pageX < that.X + (that.W / 2)) && (e.pageY > that.Y - (that.H / 2) && e.pageY < that.Y + (that.H / 2))) {
if (that.isNotTransparent(e)) {
that.onClick();
}
}
}
This function (also inside the Afbeelding function) is used to check the pixel for transparancy.
that.isNotTransparent = function (e) {
var result = false;
ctx2.clearRect(0, 0, width, height);
that.draw(ctx2);
var imgData = ctx2.getImageData(e.pageX, e.pageY, 1, 1);
ctx2.clearRect(0, 0, width, height);
if (imgData.data[3] > 0) {
result = true;
}
return result;
}
}
And all below is to lauch the things up above.
var items = new Array();
var afb = new Afbeelding();
afb.draw();
afb.onClick = function () {
alert('clicked');
}
items.push(afb);
document.addEventListener('mousedown', function (e) {
items.forEach(function (item, index) {
item.clicked(e);
});
});
I have a canvas with some irregular shape drawings in it, and I would like to have a feedback when someone clicks on a specific one?
I've been looking everywhere for this and have only found solutions for rectangle.
I think it may have to do with isPointInPath(), but I've yet to find a concise explanation on how to use it.
Any help welcome.
I made a tutorial that uses a second invisible canvas to do object picking/hit testing. Draw all your shapes, one by one, onto the second canvas until one of them has a black pixel where the mouse location is. Then you've found your object!
Here's a bit from the tutorial I wrote on selecting objects with canvas:
// gctx is ghost context, made from the second canvas
// clear(gctx)
// ...
// run through all the boxes
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black', 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
My full demo only uses rectangles but in a later version I will use circles/paths/text.
If you want to see the demo and my full code it is here.
You can use a pathiterator, that turns all shapes into approximated polygons.
Then use a 'point in polygon' algorithm to check if the point is in the shape.
This can be achived by using Path2D.
var div = document.getElementById("result");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var path1 = new Path2D();
path1.rect(10, 10, 100, 100);
path1.closePath();
ctx.stroke(path1);
var path2 = new Path2D();
path2.moveTo(220, 60);
path2.arc(170, 60, 50, 0, 2 * Math.PI);
path2.closePath();
ctx.stroke(path2);
var path3 = new Path2D("M230 10 h 80 v 80 h -80 Z");
ctx.fill(path3);
path3.closePath();
$('canvas').click( function(event)
{
div.innerHTML = "";
var x = event.pageX;
var y = event.pageY;
if (ctx.isPointInPath(path1, x, y))
div.innerHTML = "Path1 clicked";
if (ctx.isPointInPath(path2, x, y))
div.innerHTML = "Path2 clicked";
if (ctx.isPointInPath(path3, x, y))
div.innerHTML = "Path3 clicked";
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<canvas id="canvas"></canvas>
<div id="result"></div>
</body>