I made a Canvas with 32x32 tiles. I wanted to stroke the tile the mouse is on, this is what I made (Thanks to some tutorials and adjustments)
First, I create cPush and cUndo. cPush is called when I draw something, to memorize it. For cUndo, you'll see later. I also call my ctx var and I call my canvas var.
var canvas = document.getElementById('canvas');
var cPushArray = new Array();
var cStep = -1;
var ctx = canvas.getContext('2d');
function cPush() {
cStep++;
if (cStep < cPushArray.length) { cPushArray.length = cStep; }
cPushArray.push(document.getElementById('canvas').toDataURL());
}
function cUndo() {
if (cStep > 0) {
cStep--;
var canvasPic = new Image();
canvasPic.src = cPushArray[cStep];
canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); }
}
}
I will skip some parts because they are not necessary.
Here, I'm calling the onmousemove function :
canvas.onmousemove = function(e) {
let rects = [], i = 0, r;
let jk = 0;
for(var nb = 0, l = map.terrain.length ; nb < l ; nb++) {
var ligne = map.terrain[nb];
var y2 = nb * 32;
for(var j = 0, k = ligne.length ; j < k ; j++, jk++) {
rects[jk] = {lignej: ligne[j], x: j * 32, y: y2, w: 32, h: 32};
}
}
This first part is made to get every tiles of the map and store it into rects[x] This will be useful for me later, when I will need specific tiles.
Now, I call a function :
map.test = function(linewidth) {
this.tileset.dessinerTile(r.lignej, context, r.x, r.y);
ctx.lineWidth = linewidth;
ctx.strokeRect(r.x, r.y, 32, 32);
}
This is made to redraw the Tile i'm on with a stroke. (r. = rects var, you'll see later)
Here's the rest :
var rect = this.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
while (r = rects[i++]) {
ctx.beginPath();
ctx.rect(r.x, r.y, 32, 32);
if (ctx.isPointInPath(x, y) == true) {
cPush();
map.test(2);
} else {
cUndo();
}
}
};
The var Rect is made to check the mouse's position.
Now, there's a while. For each tiles we'll check if isPointinPath is true. (x and y are the mouse's positions).
So, if that's true, the function map.test will draw the tile with a 2 lineWidth stroke as mentioned.
If not, it will Undo the last stroke.
What I wanted to do here :
If the mouse is on a tile, it stroke the tile. If the mouse move, it undo the last stroke and stroke the new one.
What it does :
If the mouse is on a tile, it stroke the tile before doing an undo of the stroke because it check the other ones. So the stroke only last 0.5s instead of being permanent until I move the mouse.
I think the problem is that I'm using a "onmousemove" function ? Do I need to change to a "onmousehover" or something like that ? I'm quite lost...
Thanks for the help !
RECORD OF THE PROBLEM (Sorry for the low FPS it's a gif)
http://recordit.co/GokcSAoMv6
Related
My question is: how should I do the logical and mathematical part, in order to calculate the position of each arc, when an event is emitted: "click", "mouseover", etc.
Points to consider:
. Line width
. They are rounded lines.
. Arc length in percentage.
. Which element is first, ej: z-index position.
My source code
Thank you for your time.
There is a convenient isPointInStroke() method, just for that.
To take the z-index into consideration, just check in the reverse order you drew:
const ctx = canvas.getContext('2d');
const centerX = canvas.width/2;
const centerY = canvas.height/2;
const rad = Math.min(centerX, centerY) * 0.75;
const pathes = [
{
a: Math.PI/4,
color: 'white'
},
{
a: Math.PI/1.5,
color: 'cyan'
},
{
a: Math.PI,
color: 'violet'
},
{
a: Math.PI*2,
color: 'gray'
}
];
pathes.forEach(obj => {
const p = new Path2D();
p.arc(centerX, centerY, rad, -Math.PI/2, obj.a-Math.PI/2);
obj.path = p;
});
ctx.lineWidth = 12;
ctx.lineCap = 'round';
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
// since we sorted first => higher z-index
for(let i = pathes.length-1; i>=0; i--) {
let p = pathes[i];
ctx.strokeStyle = p.hovered ? 'green' : p.color;
ctx.stroke(p.path);
};
}
function checkHovering(evt) {
const rect = canvas.getBoundingClientRect();
const x = evt.clientX - rect.left;
const y = evt.clientY - rect.top;
let found = false;
pathes.forEach(obj => {
if(found) {
obj.hovered = false;
}
else {
found = obj.hovered = ctx.isPointInStroke(obj.path, x, y);
}
});
draw();
}
draw();
canvas.onmousemove = canvas.onclick = checkHovering;
canvas{background: lightgray}
<canvas id="canvas"></canvas>
And if you need IE support, this polyfill should do.
It's much better to "remember" the objects you drew, rather than drawing them and trying to gather what they are from what you drew. So, for example, you could store the render information: (I don't know typescript)
let curves = [{start: 30, length: 40, color: "white"}/*...*/];
Then, render it:
ctx.fillStyle = curve.color;
ctx.arc(CENTER_X, CENTER_Y, RADIUS, percentToRadians(curve.start), percentToRadians(curve.start + curve.length));
Then, to retrieve the information, simply reference curves. The z values depend on the order of the render queue (curves).
Of course, you probably could gather that data from the canvas, but I wouldn't recommend it.
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 created render method after canvas image x or y is changed. But only one image is drawn after changing position, the second one isn't drawn. It also depends in what order I add that images on the screen.
App.js
my_Doge = game.addSprite("Doge", "mydoggy", 0, 700, 100, 100);
my_Cat = game.addSprite("Cat", "mycat", 0, 0, 100, 100);
function update(){
game.setPos(my_Cat, my_Cat.xcoord+=2, my_Cat.ycoord+=2);
game.setPos(my_Doge, my_Doge.xcoord+=2, my_Doge.ycoord-=2);
}
Library.js
moopleGame.prototype.setPos = function(sprite, newX, newY)
{ // Set new sprite position
sprite.xcoord = newX;
sprite.ycoord = newY;
for(var i = 0; i < this.addedSprites.length; i++)
{
this.sprite_index = 0;
if(sprite.id == this.addedSprites[i].id) // Find index of that image in array
{
this.sprite_index = i;
}
}
this.render(this.sprite_index, sprite.width, sprite.height, sprite.xcoord, sprite.ycoord);
}
moopleGame.prototype.render = function(index, width, height, x, y) // Draw image to screen
{
ctx = this.ctx;
this.fillCanvas();
this.addedSprites[index].x = x;
this.addedSprites[index].y = y;
var w = new Image();
w.src = this.addedSprites[index].src;
ctx.drawImage(w, this.addedSprites[index].x,
this.addedSprites[index].y,
this.addedSprites[index].width,
this.addedSprites[index].height);
}
Preview
I created circles using canvas but can't delete the same on double click.`
var canvas,
context, shapes,
dragging = false, draggingtoMove = false,
dragStartLocation,dragEndLocation,
snapshot;
var numShapes;
function initiate() {
numShapes = 100;
shapes = [];
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
canvas.addEventListener('dblclick', dblclickerase);
}
function dblclickerase(evt)
{
var i;
//We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
//only the topmost one will be dragged.
var highestIndex = -1;
//getting mouse position correctly, being mindful of resizing that may have occured in the browser:
var bRect = canvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left)*(canvas.width/bRect.width);
mouseY = (evt.clientY - bRect.top)*(canvas.height/bRect.height);
//find which shape was clicked
for (i=0; i < shapes.length; i++) {
if (hitTest(shapes[i], mouseX, mouseY)) {
//draggingtoMove = true;
if (i > highestIndex) {
// here i want to delete the circle on double click but not getting logic, I can get mouse location but how to select the circle and delete it
}
}
}
}
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {
x: x,
y: y
};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function draw(position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
var i=0;
var tempX;
var tempY;
var tempRad;
var tempR;
var tempG;
var tempB;
var tempColor;
tempRad = radius;
tempX = dragStartLocation.x;
tempY = dragStartLocation.y;
tempColor = getRndColor();
tempShape = {x:tempX, y:tempY, rad:tempRad, color:tempColor};
shapes.push(tempShape);
context.beginPath();
context.arc(tempX, tempY, tempRad, 0, 2*Math.PI, false);
//context.closePath();
context.fillStyle = tempColor;
context.fill();
i++;
}
function dragStart(evt) {
dragging = true;
//if (dragging == true) {
dragStartLocation = getCanvasCoordinates(evt);
takeSnapshot();
//}
}
function hitTest(shape,mx,my) {
var dx;
var dy;
dx = mx - shape.x;
dy = my - shape.y;
//a "hit" will be registered if the distance away from the center is less than the radius of the circular object
return (dx*dx + dy*dy < shape.rad*shape.rad);
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
draw(position);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
dragEndLocation = getCanvasCoordinates(event);
draw(position);
}
function getRndColor() {
var r = 255 * Math.random() | 0,
g = 255 * Math.random() | 0,
b = 255 * Math.random() | 0;
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
function eraseCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
addEventListener("load",initiate);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<canvas id="canvas" width="1020" height="640"></canvas>
<button onclick="eraseCanvas()" style="float: right;">Reset</button>
</body>
</html>
My question is how to delete the circle when double click on same, I
added 'dblClick' eventListener but still I am only able to perform the 'clearRect'
which will only clear the rectangle from start and end location which is little odd. Another thing I can't change the color to white which will not be valid.point as if my circle overlaps another will look odd.
You can't delete what you draw on the canvas like that. Once it's drawn on the canvas, it stays there and you have no way to access it except to read the pixel data - but that won't solve your problem because you can have overlapping circles of the same color.
To solve the issue, you must keep track of drawn circles, and redraw the full canvas every time it's needed (when adding a new circle, removing an old one, etc.). That way, when you want to delete a circle, you'd simply remove it from the list of circles (a simple array would work). But the important thing is that you need to clear and redraw the full canvas.
Summary: while having your canvas constantly redrawn (either on every tick or when a user interaction happens), your click'n'drag function should only be adding the circle to the circle list (specifying data like x, y, radius, color), while double-clicking a circle would look up that circle in the list, and remove it.
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>