Remove context Arc from canvas - javascript

I am working on a project where user upload structural diagram(engineering diagram). When I user double click on the intended location on canvas the speech to text engine turns on and listen for user comments and then it draw a small circle with different colors and fill text (count) on that location. I am saving comments, counts, coordinates of arc and other things in react state and displaying the list in a component with edit and delete button. When user press the delete button. comment and other property gets deleted from the state.
I want to remove the drawn arc from the canvas. How can I do it?
I have tried clearRect. But it is not working in this case.
Please let me know.
componentDidMount() {
const img = this.refs.image;
const canvas = this.refs.canvas;
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
img.onload = () => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.font = "40px Courier";
ctx.fillText('Drawing 1', 200, 100);
}
}
drawCircle(x, y, getcolor) {
const canvas = this.refs.canvas;
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2, false);
ctx.strokeStyle = "black";
ctx.stroke();
ctx.fillStyle = getcolor;
ctx.fill();
ctx.closePath();
// Text
ctx.beginPath();
ctx.font = "20px Arial"
ctx.fillStyle = "#00538a";
ctx.textAlign = "center";
ctx.fillText(this.state.count, x , y - 20);
ctx.fill();
ctx.closePath();
}
remove(id) {
this.setState({
comments: this.state.comments.filter(c => c.id !== id)
});
const canvas = this.refs.canvas;
const ctx = canvas.getContext('2d');
const arc = this.state.comments.filter(c => c.id === id);
let x = arc[0].coordinates.x;
let y = arc[0].coordinates.y
console.log("TCL: Drawing -> remove -> arc", arc[0].coordinates);
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2, false);
ctx.clip();
ctx.clearRect(x-8, y-8, 16,16);
}
Thanks
Meet

As I mentioned in my comments the way you're trying to remove a circle from the canvas ain't gonna work.
If you call clearRect() on the canvas, it will essentially overwrite the target area including your original background image.
Instead you need to keep track of the circles - more precisely the position at which those should be drawn - using an array.
If you click the canvas -> add a circle element to an array -> clear the canvas -> draw the diagram again -> loop over the array to draw the circles on top
If you click the remove button of a circle -> search the array for this particular circle -> remove it from the array -> clear the canvas -> draw the diagram again -> loop over the array to draw the circles on top
Here's an example to illustrate what I'm talking about:
var comments = new Array();
var canvas = document.createElement("canvas");
canvas.style="float:left;"
canvas.width = 400;
canvas.height = 200;
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function updateCanvas() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.font = "40px Courier";
ctx.fillText('Drawing 1', 200, 100);
for (var a = 0; a < comments.length; a++) {
ctx.beginPath();
ctx.arc(comments[a].x, comments[a].y, 8, 0, Math.PI * 2, false);
ctx.strokeStyle = "black";
ctx.stroke();
ctx.fillStyle = "black";
ctx.fill();
ctx.closePath();
}
}
var img = new Image();
img.onload = () => {
updateCanvas();
}
img.src = "https://picsum.photos/id/59/400/200";
function addCircle(e) {
var div = document.createElement("div");
div.innerHTML = "remove" + comments.length;
document.body.appendChild(div);
div.addEventListener("click", function(e) {
for (var a = 0; a < comments.length; a++) {
if (comments[a].div == e.target) {
comments.splice(a, 1);
break;
}
}
document.body.removeChild(e.target);
updateCanvas();
});
comments.push({
x: e.clientX,
y: e.clientY,
div: div
});
updateCanvas();
}
canvas.addEventListener("click", addCircle);
Everytime you click on the picture a 'remove' div will be created to the right of the canvas. if you click it, the associated circle will be removed.

Related

Clear canvas after ctx.arc was clicked

So the objective is to place a circle randomly on the screen, and if the circle was clicked, remove this old circle and make a new circle.
I'm having a problem with removing the old point and making a new one. Instead of removing the old one, it keeps it, makes a new circle and eventually does whatever this is after a bit of clicking:
This is how my code looks like:
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.cursor = "crosshair";
function randSpot() {
var X = Math.floor(Math.random() * canvas.width) + 10;
var Y = Math.floor(Math.random() * canvas.height) + 10;
ctx.arc(X, Y, 5.5, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
document.body.addEventListener('click', function(e) {
if (ctx.isPointInPath(e.clientX, e.clientY)) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
randSpot()
}
});
}
randSpot()
What am I doing wrong and how can I fix it?
You forgot to begin a new path
ctx.beginPath();
I had to modify the code to make it run in the snippet
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.cursor = "crosshair";
function randSpot() {
var X = Math.floor(Math.random() * canvas.width) + 10;
var Y = Math.floor(Math.random() * canvas.height) + 10;
ctx.beginPath();
ctx.arc(X, Y, 5.5, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
}
randSpot()
document.body.addEventListener('click', function(e) {
// I had to remove the following condition
// ctx.isPointInPath(e.clientX, e.clientY)
// because the code didn't wanted to work with the snippet
// but it's unrelated to the problem
ctx.clearRect(0, 0, canvas.width, canvas.height);
randSpot()
});

Filling in two colors while using the rect() method

I was trying to make two different shapes that are different colors but it isn't working. Both of the shapes are the same colors. Please help!(Please note that I am not the best coder in the world)
I've looked for other examples on this website, but all of them use the lineTo() method and I would like to use the rect() method just to make things easier.
//make canvas and set it up
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.style.position = 'absolute';
canvas.style.left = '0px';
canvas.style.top = '0px';
canvas.style.backgroundColor = '#D0C6C6';
var cH = canvas.height;
var cW = canvas.width;
//draw paddles
//variables
var paddleLength = 120;
var redPaddleY = window.innerHeight / 2;
var bluePaddleY = window.innerHeight / 2;
var paddleWidth = 20;
//drawing starts
function drawPaddles() {
//RED PADDLE
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
//BLUE PADDLE
var bluePaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12 * 14, bluePaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
redPaddle('red');
bluePaddle('blue');
};
var interval = setInterval(drawPaddles, 25);
Whenever you add a shape to the canvas it becomes part of the current path. The current path remains open until you tell the canvas to start a new one with beginPath(). This means that when you add your second rect() it is combined with the first and filled with the same colour.
The simplest fix would be to use the fillRect() function instead of rect which begins, closes and fills a path in one call.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.fillRect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
};
If you still want to use rect() you should tell the canvas to begin a new path for each paddle.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
I would also suggest moving the clearRect() outside of the drawing functions too. Clear once per frame and draw both paddles.
...
ctx.clearRect(0, 0, cW, cH);
redPaddle();
bluePaddle();
...
You should also investigate requestAnimationFrame() to do your animation loop as it provides many performance improvements over intervals.

Erasing only paticular element of canvas in JS

I want to create something like scratch card.
I created a canvas and added text to it.I than added a box over the text to hide it.Finally write down the code to erase(scratch) that box.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,50);
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle='red';
ctx.fillRect(0,0,500,500);
function myFunction(event) {
var x = event.touches[0].clientX;
var y = event.touches[0].clientY;
document.getElementById("demo").innerHTML = x + ", " + y;
ctx.globalCompositeOperation = 'destination-out';
ctx.arc(x,y,30,0,2*Math.PI);
ctx.fill();
}
But the problem is it delete the text also.
How could I only delete that box not the text?
Canvas context keeps only one drawing state, which is the one rendered. If you modify a pixel, it won't remember how it was before, and since it has no built-in concept of layers, when you clear a pixel, it's just a transparent pixel.
So to achieve what you want, the easiest is to build this layering logic yourself, e.g by creating two "off-screen" canvases, as in "not appended in the DOM", one for the scratchable area, and one for the background that should be revealed.
Then on a third canvas, you'll draw both canvases every time. It is this third canvas that will be presented to your user:
var canvas = document.getElementById("myCanvas");
// the context that will be presented to the user
var main = canvas.getContext("2d");
// an offscreen one that will hold the background
var background = canvas.cloneNode().getContext("2d");
// and the one we will scratch
var scratch = canvas.cloneNode().getContext("2d");
generateBackground();
generateScratch();
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
main.clearRect(0,0,canvas.width,canvas.height);
main.drawImage(background.canvas, 0,0);
main.drawImage(scratch.canvas, 0,0);
}
function generateBackground(){
background.font = "30px Arial";
background.fillText("Hello World",10,50);
}
function generateScratch() {
scratch.fillStyle='red';
scratch.fillRect(0,0,500,500);
scratch.globalCompositeOperation = 'destination-out';
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
scratch.beginPath();
scratch.arc(x, y, 30, 0, 2*Math.PI);
scratch.fill();
drawAll();
}
<canvas id="myCanvas"></canvas>
Now, it could all have been done on the same canvas, but performance wise, it's probably not the best, since it implies generating an overly complex sub-path that should get re-rendered at every draw, also, it is not much easier to implement:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
ctx.globalCompositeOperation = 'source-over';
// first draw the scratch pad, intact
ctx.fillStyle = 'red';
ctx.fillRect(0,0,500,500);
// then erase with the currently being defined path
// see 'handlemousemove's note
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
// finally draw the text behind
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'black';
ctx.fillText("Hello World",10,50);
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
// note how here we don't create a new Path,
// meaning that all the arcs are being added to the single one being rendered
ctx.moveTo(x, y);
ctx.arc(x, y, 30, 0, 2*Math.PI);
drawAll();
}
<canvas id="myCanvas"></canvas>
How could I only delete that box not the text?
You can't, you'll have to redraw the text. Once you've drawn the box over the text, you've obliterated it, it doesn't exist anymore. Canvas is pixel-based, not shape-based like SVG.

How can I stop HTML5 Canvas Ghosting?

I made a small program that:
changes the mouse cursor inside the canvas to a black square
gives the black square a nice trail that fades away over time (the point of the program)
Here's the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.save();
ctx.globalAlpha = 0.1; // the opacity (i.e. fade) being applied to the canvas on each function re-run
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
ctx.fillStyle = "black";
ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
Here's a live example: https://jsfiddle.net/L6j71crw/2/
Problem
However the trail does not fade away completely, and leaves a ghosting trail.
Q: How can I remove the ghosting trail?
I have tried using clearRect() in different ways, but it just clears the entire animation leaving nothing to display. At best it just removes the trail and only fades the square cursor alone, but it still doesn't make the cursor completely transparent when the fading process is completed. I have tried finding posts about it, but I found nothing that gave a definitive answer and—most importantly—no posts with a working example.
Any ideas?
Try having a list of positions, this won't leave a ghost trail!
my code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var Positions = [];
var maxlength = 20;
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
var V2 = function(x, y){this.x = x; this.y = y;};
function getMousePos(canvas, e) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var e = 0; e != Positions.length; e++)
{
ctx.fillStyle = ctx.fillStyle = "rgba(0, 0, 0, " + 1 / e + ")";
ctx.fillRect(Positions[e].x, Positions[e].y, 8, 8);
}
if(Positions.length > 1)
Positions.pop()
//ctx.save();
//ctx.globalAlpha = 0.5; // the opacity (i.e. fade) being applied to the canvas on each function re-run
//ctx.fillStyle = "#fff";
//ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
//ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
Positions.unshift(new V2(pos.x, pos.y));
if(Positions.length > maxlength)
Positions.pop()
//ctx.fillStyle = "black";
//ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
JSFiddle: https://jsfiddle.net/L6j71crw/9/
Edit: made the cursor constant.

Canvas - clipping multiple images

I want to clip a bunch of images into hexagon shapes.
I have it sort of working, but the clipping is across all the hexes instead of each image clipping to only one hex. What am I doing wrong?
Here's a live demo:
http://codepen.io/tev/pen/iJaHB
Here's the js in question:
function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise, img, imgX, imgY) {
if (sides < 3) return;
var a = (Math.PI * 2)/sides;
a = anticlockwise?-a:a;
ctx.save();
ctx.translate(x,y);
ctx.rotate(startAngle);
ctx.moveTo(radius,0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius*Math.cos(a*i),radius*Math.sin(a*i));
}
ctx.closePath();
// add stroke
ctx.lineWidth = 5;
ctx.strokeStyle = '#056e96';
ctx.stroke();
// add stroke
ctx.lineWidth = 4;
ctx.strokeStyle = '#47b6c8';
ctx.stroke();
// add stroke
ctx.lineWidth = 2;
ctx.strokeStyle = '#056e96';
ctx.stroke();
// Clip to the current path
ctx.clip();
ctx.drawImage(img, imgX, imgY);
ctx.restore();
}
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
var img2 = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
polygon(ctx, 120,120,100,6, 0,0,img, -120,-170);
}
img2.onload = function () {
polygon(ctx, 280,212,100,6, 0,0,img2, -150,-120);
}
// Specify the src to load the image
img.src = "http://farm8.staticflickr.com/7381/9601443923_051d985646_n.jpg";
img2.src = "http://farm6.staticflickr.com/5496/9585303170_d005d2aaa9_n.jpg";
You need to add this to your polygon() method:
ctx.beginPath();
See modified pen here
function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise, img, ...
if (sides < 3) return;
var a = (Math.PI * 2)/sides;
a = anticlockwise?-a:a;
ctx.save();
ctx.translate(x,y);
ctx.rotate(startAngle);
ctx.beginPath(); /// for example here, before moveTo/lineTo
ctx.moveTo(radius,0);
...
If not the lines will accumulate so the second time you call polygon the previous polygon will still exist. That's why you see the image partly inside the first hexagon as well.

Categories

Resources