How can I stop HTML5 Canvas Ghosting? - javascript

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.

Related

Clear canvas on window resize

I am trying to reset my progress ring, which is drawn with canvas on resize.
Currently, when I resize my window the function is run again as expected, but instead of the canvas being reset another progress ring is being drawn within the same canvas, please see screenshot below:
I have found clearRect() found in an answer here: How to clear the canvas for redrawing and similar solutions but it doesn't seem to resolve my issue.
Please find my codepen with the code: https://codepen.io/MayhemBliz/pen/OJQyLbN
Javascript:
// Progress ring
function progressRing() {
const percentageRings = document.querySelectorAll('.percentage-ring');
for (const percentageRing of Array.from(percentageRings)) {
console.log(percentageRing.offsetWidth)
var percentageRingOptions = {
percent: percentageRing.dataset.percent,
size: percentageRing.offsetWidth,
lineWidth: 30,
rotate: 0
}
var canvas = document.querySelector('.percentage-ring canvas');
var span = document.querySelector('.percentage-ring span');
span.textContent = percentageRingOptions.percent + '%';
if (typeof (G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
//clear canvas for resize
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
// end clear canvas
canvas.width = canvas.height = percentageRingOptions.size;
percentageRing.appendChild(span);
percentageRing.appendChild(canvas);
ctx.translate(percentageRingOptions.size / 2, percentageRingOptions.size / 2); // change center
ctx.rotate((-1 / 2 + percentageRingOptions.rotate / 180) * Math.PI); // rotate -90 deg
//imd = ctx.getImageData(0, 0, 240, 240);
var radius = (percentageRingOptions.size - percentageRingOptions.lineWidth) / 2;
var drawCircle = function (color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
//ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#efefef', percentageRingOptions.lineWidth, 100 / 100);
var i = 0; var int = setInterval(function () {
i++;
drawCircle('#555555', percentageRingOptions.lineWidth, i / 100);
span.textContent = i + "%";
if (i >= percentageRingOptions.percent) {
clearInterval(int);
}
}, 50);
}
}
window.addEventListener('load', progressRing);
window.addEventListener('resize', progressRing);
HTML:
<div class="percentage-ring" data-percent="88">
<span>88%</span>
<canvas></canvas>
</div>
Your help would be greatly appreciated.
Thanks in advance
1. Clear size problem
var canvas = document.querySelector('.percentage-ring canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
You have to specify canvas size with (canvas.width, canvas.height) instead of ctx.canvas.width, ctx.canvas.height
ctx.clearRect(0, 0, canvas.width, canvas.height);
2. Interval timer usage problem
If progressRing() is called again after setInterval() without clearInterval(), e.g., due to resizing, it will continue to run with the previous interval execution surviving. This will cause the ring to be drawn twice, thrice and more.
Place var int = 0; outside functions. This initializes the int to 0 first.
And modify var int = setInterval( to int = setInterval(
Then place the following code at the beginning of progressRing()
if (int) {
clearInterval(int);
int = 0;
}
And also place int = 0; immediately after the clearInterval() call that was already there.

Remove context Arc from canvas

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.

Javascript: Navigatable Viewport that can also be interacted with

I am in the process of learning javascript for game design, and wanted to make a separate achievement page that the user can navigate to that will allow them to check their achievements on various games. (at the moment I am not concerned with implementing localstorage/cookies etc, I just want to work on the page for now)
So the requirements of my base idea is as follows:
Able to drag around the viewport/page to view all the achievement categories as they will likely not all be in view on smaller screens
Able to click on a category to open a small box containing all achievements belonging to that game/category
Able to mouse over all achievements in the boxes to get text descriptions of what they are
OPTIONAL: have lines connecting each box on the "overworld" to show users where nearby boxes are if they are off screen
At first, I thought I would need canvas to be able to do this. I learned a bit about it and got decently far until I realized that canvas has a lot of restrictions like not being able to do mouseover events unless manually implementing each one. Here is the current progress I was at in doing a test-run of learning canvas, but it's not very far:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
window.addEventListener("resize", resize);
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var panning = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
var canvasCenterWidth = (canvas.width / 2);
var canvasCenterHeight = (canvas.height / 2);
function draw() {
ctx.beginPath();
ctx.rect(canvasCenterWidth, canvasCenterHeight, 100, 100);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.beginPath();
ctx.arc(350, 250, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
function pan() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
function startPan(e) {
window.addEventListener("mousemove", trackMouse);
window.addEventListener("mousemove", pan);
window.addEventListener("mouseup", endPan);
panning.start.x = e.clientX;
panning.start.y = e.clientY;
}
function endPan(e) {
window.removeEventListener("mousemove", trackMouse);
window.removeEventListener("mousemove", pan);
window.removeEventListener("mouseup", endPan);
panning.start.x = null;
panning.start.y = null;
global.offset.x = panning.offset.x;
global.offset.y = panning.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - panning.start.x;
var offsetY = e.clientY - panning.start.y;
panning.offset.x = global.offset.x + offsetX;
panning.offset.y = global.offset.y + offsetY;
}
body{
overflow: hidden;
}
canvas {
top: 0;
left: 0;
position: absolute; }
<canvas id="canvas"></canvas>
So I guess my question is now: what is the best way to implement this? Is it feasable to do it with canvas, or should I just scrap that and try to figure out something with div movement? Should I be concerned with performance issues and should that affect how I implement it?

JavaScript canvas clipping shape when out of bounds

What I'm asking for may be extremely easy, but I've been having quite a bit of trouble getting the intended result.
I want a shape (in this example it's squares but should work with other shapes such as circles, etc) to cut itself off when it leaves the bounds of another shape.
Basically, top image is what I want, bottom is what I currently have:
http://imgur.com/a/oQkzG
I heard this can be done with globalCompositeOperation, but am looking for any solution that will give the wanted result.
This is the current code, if you can't use JSFiddle:
// Fill the background
ctx.fillStyle = '#0A2E36';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Fill the parent rect
ctx.fillStyle = '#CCA43B';
ctx.fillRect(100, 100, 150, 150);
// Fill the child rect
ctx.fillStyle = 'red';
ctx.fillRect(200, 200, 70, 70);
// And fill a rect that should not be affected
ctx.fillStyle = 'green';
ctx.fillRect(80, 80, 50, 50);
JSFiddle Link
Since you need some kind of relation between objects - a scene graph -, you should build it now.
From your question, it seems that any child element should be drawn clipped by its parent element.
(Yes composite operation could come to the rescue, but they are handy only when drawing like 2 figures on a cleared background, things get quickly complicated otherwise, and you might have to use a back canvas, so clipping is simpler here.)
I did below a most basic class that handles the rect case, you'll see that it isn't very difficult to build.
The 'scene' is made out of a background Rect, which has two childs, the yellow and the green. And the yellow Rect has a red child.
var canvas = document.getElementById('cv');
var ctx = canvas.getContext('2d');
function Rect(fill, x, y, w, h) {
var childs = [];
this.draw = function () {
ctx.save();
ctx.beginPath();
ctx.fillStyle = fill;
ctx.rect(x, y, w, h);
ctx.fill();
ctx.clip();
for (var i = 0; i < childs.length; i++) {
childs[i].draw();
}
ctx.restore();
}
this.addChild = function (child) {
childs.push(child);
}
this.setPos = function (nx, ny) {
x = nx;
y = ny;
}
}
// background
var bgRect = new Rect('#0A2E36', 0, 0, canvas.width, canvas.height);
// One parent rect
var parentRect = new Rect('#CCA43B', 100, 100, 150, 150);
// child rect
var childRect = new Rect('red', 200, 200, 70, 70);
parentRect.addChild(childRect);
// Another top level rect
var otherRect = new Rect('green', 80, 80, 50, 50);
bgRect.addChild(parentRect);
bgRect.addChild(otherRect);
function drawScene() {
bgRect.draw();
drawTitle();
}
drawScene();
window.addEventListener('mousemove', function (e) {
var rect = canvas.getBoundingClientRect();
var x = (e.clientX - rect.left);
var y = (e.clientY - rect.top);
childRect.setPos(x, y);
drawScene();
});
function drawTitle() {
ctx.fillStyle = '#DDF';
ctx.font = '14px Futura';
ctx.fillText('Move the mouse around.', 20, 24);
}
<canvas id='cv' width=440 height=440></canvas>

I want to draw pattern on an image with canvas on click

i want to draw pattern on a image with canvas on click you can understand more with the provided images
here is what end result i want
http://i.imgur.com/wnH2Vxu.png
But i am having this blurred line
http://i.imgur.com/HXF1rTv.png
i am using following code
(
function() {
var canvas = document.querySelector('#canvas');
var ctx = canvas.getContext('2d');
var sketch = document.querySelector('#sketch');
var sketch_style = getComputedStyle(sketch);
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
var mouse = {x: 0, y: 0};
var last_mouse = {x: 0, y: 0};
/* Mouse Capturing Work */
canvas.addEventListener('mousemove', function(e) {
last_mouse.x = mouse.x;
last_mouse.y = mouse.y;
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
}, false);
var texture = document.getElementById("texture");
pFill = ctx.createPattern(texture, "repeat");
ctx.strokeStyle = pFill;
/* Drawing on Paint App */
ctx.lineWidth = 12;
ctx.lineJoin = 'square';
ctx.lineCap = 'square';
canvas.addEventListener('mousedown', function(e) {
canvas.addEventListener('mousemove', onPaint, false);
}, false);
canvas.addEventListener('mouseup', function() {
canvas.removeEventListener('mousemove', onPaint, false);
}, false);
var onPaint = function() {
ctx.beginPath();
ctx.moveTo(last_mouse.x, last_mouse.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.closePath();
ctx.stroke();
};
}()
);
$( document ).ready(function() {
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
ctx.drawImage(img,10,10);
});
Method with stroke pattern will not work here. Because this pattern position is fixed. So when your line is not exactly horizontal or vertical you will have a distorted image instead of a chain of accurate balls.
Well I think the whole approach should be reconsidered.
User should create a path like in photoshop, which is visible as a temporary line. At first it can easily be a polyline, where vertices are pointed by clicks. And when the path is confirmed (by double click for example) you should remove the temporary line and put the balls along the path with a given step. In this case you can handle all turns and regularity distortions.
This is quite a bunch of work, but the task is in reality much more complex than it seems to be.
As a quick workaround you can try a simple approach. Drop a ball each time distance from the previous drop is more than double radius (for example) of the ball..
var lastBall = {x: null, y : null};
function drawBall(center){
ctx.beginPath();
ctx.arc(center.x, center.y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'orange';
ctx.fill();
}
function distance(a, b){
return Math.sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) );
}
var onPaint = function() {
if(!lastBall.x || distance(lastBall, mouse) > 25 ){
drawBall(mouse);
lastBall.x = mouse.x;
lastBall.y = mouse.y;
}
The code is rough you should define proper variables for color and radius, or perhaps replace it with your pattern, but you can get the idea
Fiddle

Categories

Resources