I try to get mouse coordinates on a canvas which has borders defined in CSS. First, I have used a solution found out on this link to get the coordinates of a mouse over a canvas :
function getPosition(event)
{
var x = new Number();
var y = new Number();
var canvas = document.getElementById("canvas");
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
else // Firefox method to get the position
{
x = event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
}
solution 1 the result of this method. You can see that coordinates have a shift of 10 pixels, i.e the width of canvas borders.
To fix this issue of border width, I modified like this :
var computedStyle = window.getComputedStyle(canvas,null);
var topBorder = parseInt(computedStyle.getPropertyValue("border-top-width"),10);
var leftBorder = parseInt(computedStyle.getPropertyValue("border-left-width"),10);
// (x,y) coordinates
x -= canvas.offsetLeft + leftBorder;
y -= canvas.offsetTop + topBorder;
But this doesn't work very well : I have a light shifting on the corners (1 or 2 px shifting) ; you can see the result of this modification on solution 2
I also tried to do :
function getPosition(event) {
var x, y;
var rect = canvas.getBoundingClientRect();
var computedStyle = window.getComputedStyle(canvas,null);
var topBorder = parseInt(computedStyle.getPropertyValue("border-top-width"),10);
var leftBorder = parseInt(computedStyle.getPropertyValue("border-left-width"),10);
var x = event.clientX - rect.left - leftBorder;
var y = event.clientY - rect.top - topBorder;
// Display coordinates
xspan.innerHTML = x;
yspan.innerHTML = y;
}
The result is shown on solution 3 and there's still a light shift of 1, 2 pixels for the inner corners of the board.
I would like put the (0,480) mouse coordinates in the inner left bottom of canvas, (0,0) at the inner left up corner, (480,0) at the right up corner and (480,480) in the right bottom corner.
How to deal with the "10 pixel" of borders width (see CSS) ?
I make you notice that, before calling getPosition, I have added 0.5px to all canvas coordinates because, for example, a line drawn at i=1 with border-width equal to 1, must be drawn at x = 0.5.
If anyone could give me advices to get valid coordinates on this canvas taking account of the borders width.
Thanks
Borders
Do as #markE said and wrap your <canvas> element inside a div:
<div id="game-wrapper">
<canvas id="game" width="481" height="481">
</canvas>
And give the border to the game-wrapper div. Also make game-wrapper to be display: inline-block; so the border lays out correctly around the canvas.
Then your whole coordinate script could be this:
function getPosition(event) {
var x = event.layerX;
var y = event.layerY;
}
Cursor
Aside from that, i think your real problem is that the cursor's pivot point is not at the tip of the cursor, but rather a couple of pixels away from it (at least in OSX). You can observe this by changing the cursor to crosshair:
cursor: crosshair;
Coordinate system
You also asked about the valid way to do a coordinate system transformation. I've found that it's good to have some function that translates the "in-game" coordinates to actual canvas coordinates and back. In your case the 0.5px line you draw:
function Vector2(x,y) {
this.x = x;
this.y = y;
}
Vector2.prototype.toGameCoordinates() {
return new Vector2(
this.x - 0.5,
this.y - 0.5
);
}
Vector2.prototype.toCanvasCoordinates() {
return new Vector2(
this.x + 0.5,
this.y + 0.5
);
}
Related
I try to let the user zoom in the canvas with a pinch gesture, it's a Javascript Canvas Game (using Intel XDK)
I got the point coordinates (relativley to the window document, saved in an array) and the scale "strength".
var scale = 1;
function scaleCanvas(sc, point) { //point["x"] == 200
//sc has value like 0.5, 1, 1.5 and so on
x = sc/scale;
scale = sc;
ctx.scale(x, x);
}
I know that I have to translate the canvas to the point coordinates, and then retranslate it again. My problem is, that the canvas is already translated. The translation values are saved in the vars dragOffX and dragOffY. Furthermore, the initial translation may be easy, but when the canvas is already scaled, every coordinate is changed.
This is the translation of the canvas when dragging/shifting the content:
var dragOffX = 0;
var dragOffY = 0;
function dragCanvas(x,y) {
dragOffX = dragOffX + x;
dragOffY = dragOffY + y;
x = x* 1/scale;
y = y* 1/scale;
ctx.translate(x,y);
}
So when the player is dragging the content for e.g. 100px to the right, dragOffX gets the value 100.
How do I translate my canvas to the correct coordinates?
It will probably be easier if you store the transformation matrix and use setTransform each time you change it - that resets the canvas transformation matrix first before applying the new transformation, so that you have easier control over the way that the different transformations accumulate.
var transform = {x: 0, y: 0, scale: 1}
function scaleCanvas(scale, point) {
var oldScale = transform.scale;
transform.scale = scale / transform.scale;
// Re-centre the canvas around the zoom point
// (This may need some adjustment to re-centre correctly)
transform.x += point.x / transform.scale - point.x / oldScale
transform.y += point.y / transform.scale - point.y / oldScale;
setTransform();
}
function dragCanvas(x,y) {
transform.x += x / transform.scale;
transform.y += y / transform.scale;
setTransform();
}
function setTransform() {
ctx.setTransform(transform.scale, 0, 0, transform.scale, transform.x, transform.y);
}
JSFiddle
Simply Use this to scale canvas on pivot point
function scaleCanvasOnPivotPoint(s, p_x , p_y) {
ctx.translate(p_x, p_y);
ctx.scale(s);
ctx.translate( -p_x, -p_y);
}
So I'm trying to implement shooting, and what I want to do is take the players x and y and use that as the base point where the projectile derives from and then it basically goes to the point pressed in the canvas. The projectile is deriving from the player, but instead of going to the cursor point it strangely ALWAYS goes right, but will go right+up or right+down depending on which direction was pressed; I've looked on-line and can't seem to find the answer.
Inside my javascript here is the shoot function:
function shoot(event){
bullets[bulletCount] = new Array(4);
bullets[bulletCount][0] = x;
bullets[bulletCount][1] = y;
bullets[bulletCount][2] = window.event.clientX;
bullets[bulletCount][3] = window.event.clientY;
bulletCount++;
}
In my javascript here is the bullet part in the update method:
for(var b = 0; b < bullets.length; b++){
if(bullets[b][0] < bullets[b][2]) bullets[b][0] += 5;
if(bullets[b][0] > bullets[b][2]) bullets[b][0] -= 5;
if(bullets[b][1] < bullets[b][3]) bullets[b][1] += 5;
if(bullets[b][1] > bullets[b][3]) bullets[b][1] -= 5;
ctx.fillRect(bullets[b][0],bullets[b][1], 8, 8);
}
and in my index.html page:
<canvas id="gameBoard" onClick="shoot(event)" width="500" height="500" tabindex="1"></canvas>
EDIT: fixed the problem, for anyone else who suffers this problem inside the shoot function change from what was originally up there to this:
function shoot(event){
var rect = canvas.getBoundingClientRect();
bullets[bulletCount] = new Array(4);
bullets[bulletCount][0] = x;
bullets[bulletCount][1] = y;
bullets[bulletCount][2] = event.clientX - rect.left;
bullets[bulletCount][3] = event.clientY - rect.top;
bulletCount++;
}
The problem lies within these two lines:
bullets[bulletCount][2] = window.event.clientX;
bullets[bulletCount][3] = window.event.clientY;
It is because that the location you save for the click is not the real location.
You see, event.clientX/Y give you the mouse coordinates relative to the top left corner of the window, but you only want the coordinates relative to the top left corner of your canvas element.
To fix this, first get to know another way to get the mouse coordinates, which is event.pageX/Y.
This will not give you the coordinates relative to the canvas, however you need this instead of event.clientX/Y because it will give you the mouse coordinates relative to the top left corner of the page, not the window, which means it doesn't matter if the user scrolls somewhere, the coordinates are always true to the page.
So, now that you're using event.pageX/Y, how to make it relative to the canvas? Well, we can just take these coordinates and subtract the top left corner coordinates of the canvas:
bullets[bulletCount][2] = window.event.pageX - canvas.offsetLeft;
bullets[bulletCount][3] = window.event.pageY - canvas.offsetTop;
Assuming that your canvas element is saved in a variable named canvas
Hope that solves it :D
var bullets = [];
var bullet_speed = 10;
call this in your init() fucntion: which could have your setInterval.
window.addEventListener('click', shoot, false);
call this in your draw function:
for (var i = 0; i < bullets.length; i++){
bullets[i].x += bullets[i].xChange;
bullets[i].y += bullets[i].yChange;
context.fillStyle ='black';
context.fillRect(bullets[i].x,bullets[i].y,4,4)
}
function shoot(event){
x = event.offsetX
y = event.offsetY
d = Math.sqrt(Math.pow(Math.abs(player.x-x),2)+Math.pow(Math.abs(player.y-y),2))
bullet = {
x : player.x,
y : player.y,
xChange : (x-player.x)/(d/bullet_speed),
yChange : (y-player.y)/(d/bullet_speed),
};
bullets.push(bullet);
}
I am working on a maze game in HTML 5.
Here is the function which I use to draw an "X" on the canvas (using the touchpad, the user will navigate the X through the labyrinth).
dim = 8;
function rect(x,y,xdim){
ctx.beginPath();
ctx.moveTo(x - xdim, y - xdim);
ctx.lineTo(x + xdim, y + xdim);
ctx.moveTo(x + xdim, y - xdim);
ctx.lineTo(x - xdim, y + xdim);
ctx.strokeStyle = '#FF5000';
ctx.stroke();
}
The problem is in the checkcollision function, mainly in this piece of code:
ctx.getImageData(checkx, checky, 16, 16);
I am looking for an optimal way to check all the space that is taken up by the "X" on the canvas.
There are more comments in the code i used.
Issues:
1. Sometimes, the X goes a little passed the border
2. Sometimes, the X doesn't get close, letting a few pixels between it and the border.
function checkcollision(checkx, checky){
var collision = 0;
/*
User presses an arrow key
Calculate where that would move the square to.
Look at all of the pixels on the maze image that the square will cover
in its new position (our square is 15x15 so we look at 225 pixels)
Are any of the pixels black (the maze is white with black borders.
If a pixel is black that means the square would collide with a border)
YES: then do not move
NO: then move
*/
var imgd = ctx.getImageData(checkx, checky, 15, 15);
pix = imgd.data;
for (var i = 0; n = pix.length, i < n; i += 4){
if (pix[i] == 0) {
collision = 1;
} else if(pix[i] == 255 && pix[i + 1] == 0){
winner = "yes";
}
}
return collision;
}
I believe the getImageData gets a rectangle which starts from the x,y position in a corner.
So maybe there is a way to draw it using the checkx and checky as the coordinates for the center and retrieve a square 16 pixels wide
Wow, writing the question made it clear:
var imgd = ctx.getImageData(checkx - xdim, checky - xdim, 16, 16);
Ty guys
I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.
Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.
I've seen a few examples but I couldn't get them working nor they seam to address my question.
Thanks in advance!
To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.
To get this result you can do the following (see full code here):
Setup variables:
var ctx = canvas.getContext('2d'),
ix = 0, iy = 0, /// image position
offsetX = 0, offsetY = 0, /// current offsets
deltaX, deltaY, /// deltas from mouse down
mouseDown = false, /// in mouse drag
img = null, /// background
rect, /// rect position
rectW = 200, rectH = 150; /// size of highlight area
Set up the main functions that you use to set size according to window size (including on resize):
/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
rect = [canvas.width * 0.5 - rectW * 0.5,
canvas.height * 0.5 - rectH * 0.5,
rectW, rectH]
update();
}
/// window resize so recalc canvas and rect
window.onresize = setSize;
The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).
First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
Then draw the complete image.
Reset alpha
Draw the peep-hole using clipping with corrected offsets for the source.
-
/// main draw
function update() {
if (img === null) return;
/// limit x/y as drawImage cannot draw with negative
/// offsets for clipping
if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;
/// clear background to clear off garbage
ctx.clearRect(0, 0, canvas.width, canvas.height);
/// make everything transparent
ctx.globalAlpha = 0.2;
/// draw complete background
ctx.drawImage(img, ix + offsetX, iy + offsetY);
/// reset alpha as we need opacity for next draw
ctx.globalAlpha = 1;
/// draw a clipped version of the background and
/// adjust for offset and image position
ctx.drawImage(img, -ix - offsetX + rect[0], /// sx
-iy - offsetY + rect[1], /// sy
rect[2], rect[3], /// sw/h
/// destination
rect[0], rect[1], rect[2], rect[3]);
/// make a nice sharp border by offsetting it half pixel
ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}
Now it's a matter of handling mouse down, move and up and calculate the offsets -
In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:
canvas.onmousedown = function(e) {
/// don't do anything until we have an image
if (img === null) return;
/// correct mouse pos
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// store current position to calc deltas
deltaX = x;
deltaY = y;
/// here we go..
mouseDown = true;
}
Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:
canvas.onmousemove = function(e) {
/// in a drag?
if (mouseDown === true) {
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// offset = current - original position
offsetX = x - deltaX;
offsetY = y - deltaY;
/// redraw what we have so far
update();
}
}
And finally on mouse up we make the offsets a permanent part of the image position:
document.onmouseup = function(e) {
/// was in a drag?
if (mouseDown === true) {
/// not any more!!!
mouseDown = false;
/// make image pos. permanent
ix += offsetX;
iy += offsetY;
/// so we need to reset offsets as well
offsetX = offsetY = 0;
}
}
For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor
To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.
Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.
However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.
I have an svg map with a g element container.
inside the g element I have items with x, y positions.
I am trying to implement a mouse wheel zoom that pans the g element so that the object under the mouse is always under the mouse. similar to the way Google maps pans the map when zooming via the mouse wheel so that you zoom to the mouse position.
I have exhausted all searches and tried many different ways to calculate out the mouse position verses the g element position.
I've tried:
var xPan = (mouse.x - (matrix.scale * mouse.x)) - matrix.panX;
var yPan = (mouse.y - (matrix.scale * mouse.y)) - matrix.panY;
pan(xPan, yPan);
I had an similar problem some time ago, with the difference that I am using canvas but because I use svg to save my transform matrix it may help you, if I post the necessary part of my code:
window.transform = svg.createSVGMatrix();
window.pt = svg.createSVGPoint();
transformedPoint = function (x, y) {
window.pt.x = x; window.pt.y = y;
return pt.matrixTransform(window.transform.inverse());
}
translate = function(dx, dy) {
window.transform = window.transform.translate(dx, dy);
}
scale = function (scaleX, scaleY) {
window.transform = window.transform.scaleNonUniform(scaleX, scaleY);
};
zoom = function (scaleX, scaleY, x, y) { //use real x and y i.e. mouseposition on element
var p = transformedPoint(x, y);
translate(x, y);
scale(scaleX, scaleY);
translate(-x, -y);
}
I hope you can use some of this code and get it to work for you
Credits going to Phrogz and his outstanding example here: https://stackoverflow.com/a/5527449/1293849