Canvas accessing coordinate of translated position of panning background image - javascript

Cannot figure this out, how to find the translated position of the background relative to the canvas. I have the characters coordinates, and I have the coordinates from a mouse click within the canvas, but can't figure out how to find the offset.
In the canvas, when I click somewhere, I get an (x,y) value from (0,0) - (650,575), the size of the window, no matter where my character is. If the character is at (2000, 1500) on the canvas, my click/touch input will always send the character up and left towards 0,0 on the background coordinate.
At first I thought I should subtract the player X position from the max width, then add an offset half the width of the screen, and do the same for the Y position, but that didn't work.
Then I tried subtracting half the width/height of the screen from the current player x,y values but that doesn't work.
Anyone point me in the right direction, it seems elementary but I can't figure it out it's been years since math class???? Thanks
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 650;
canvas.height = 575;
var WIDTH=5000; //level width
var HEIGHT=3750; //level height
ctx.translate(-WIDTH*.5,-HEIGHT*.5); //starts in center of background
Where my player begins on load:
hero.x = WIDTH*.5+325; //offset half canvas width
hero.y = HEIGHT*.5+275; //offset half canvas height
For the Background:
ctx.drawImage(bgImage, BGsrcX , BGsrcY, 1250 , 938 ,-150, -150, BGdestW, BGdestH); `//image is stretched to 5000x3750`
This is the mouse input I'm using
if(navigator.userAgent.match(/(iPhone)|(iPod)|(iPad)/i)){
document.addEventListener('touchstart', function(e) {
if(e.touches.length == 1){ // Only deal with one finger
var touch = e.touches[0]; // Get the information for finger #1
var x = touch.pageX - canvas.offsetLeft;
var y = touch.pageY - canvas.offsetTop;
//clickEvent(x,y); //call your function to manage tweets
}
},false);
}
else{
document.addEventListener('mousedown',function(e) {
var x = e.pageX - canvas.offsetLeft;
var y = e.pageY - canvas.offsetTop;
console.log(x+":"+y);
clickEvent(x,y); //call your function to manage tweets
},false);
}
For the keyboard input to actually pan the background:
if(16 in keysDown && 38 in keysDown && hero.y > 200) {ctx.translate(0,12); }

Don't work with half-translated and non-translated coordinates, translate your mouse click coordinates AND your canvas coordinates.
Then you can just use simple subtraction to find the offset, and to find the distance, you you use the distance formula.

Related

Multiple canvas polygon detection map using isPointInPath() and responsive design

I have an array of canvas objects that draw correctly. i have three problems:
Offset. I have tested the code below in JS fiddle and it works, but when i export it my web page, the variables get skewed. The detection happens, but not in the right place. the page width is set in CSS, and the actual canvas area is centered using a margin:0 auto call, however it is smaller than the page width.
<canvas id="canvas" width="780" height="690" style="position:absolute;"></canvas>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
var $results = $("#results");
// define the polygon items
var polyArray = new Array (6);
polyArray [0] =[{x:50,y:236}, {x:200,y:115}, {x:350,y:50}, {x:350,y:300}, {x:232,y:325}, {x:75,y:300}];
polyArray [1] =[{x:350,y:55}, {x:350,y:300}, {x:510,y:300}, {x:510,y:205}, {x:578,y:172}, {x:690,y:96}, {x:650,y:17}];
polyArray [2] =[{x:510,y:300}, {x:510,y:200}, {x:715,y:113}, {x:780,y:200}, {x:780,y:485}, {x:625,y:468}, {x:605,y:456}, {x:605,y:428}];
polyArray [3] =[{x:0,y:446}, {x:284,y:320}, {x:255,y:540}, {x:240,y:566}, {x:73,y:600}, {x:0,y:565}];
polyArray [4] =[{x:355,y:305}, {x:510,y:305}, {x:604,y:423}, {x:604,y:460}, {x:628,y:484}, {x:610,y:513}, {x:587,y:468}, {x:537,y:426}, {x:500,y:400}, {x:447,y:424}, {x:312,y:365}, {x:307,y:314 }];
polyArray [5] =[{x:350,y:425}, {x:415,y:421}, {x:455,y:434}, {x:495,y:411}, {x:550,y:444}, {x:618,y:590}, {x:570,y:616}, {x:359,y:597}, {x:333,y:522}];
// call the function to draw all the objects in the array
define(polyArray);
// call through the array to draw the objects
function define(polygon) {
ctx.beginPath();
for (var i = 0; i < polygon.length; i++) {
ctx.moveTo(polygon[i][0].x, polygon[i][0].y);
for (var j = 1; j < polygon[i].length; j++) {
ctx.lineTo(polygon[i][j].x, polygon[i][j].y);
}
ctx.fill();
}
ctx.closePath();
}
function hitTest(polygon) {
// redefine the polygon
define(polygon);
// ask isPointInPath to hit test the mouse position
// against the current path
return (ctx.isPointInPath(mouseX, mouseY));
}
function handleMouseMove(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// check if the mouse is inside the polygon
var isInside = hitTest(polyArray);
if (isInside) {
canvas.style.cursor = 'pointer';
$results.text("Mouse is inside the area);
} else {
canvas.style.cursor = 'default';
$results.text("Outside");
}
}
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
Detecting which object has been hovered over. What needs to happen is on hover of one the array shapes should effect some CSS/JS. How can i assign an ID variable and detect it?
when i bring responsive design into the equation i'm a bit stuck for how to incorporate this offset and the poly co-ords to scale appropriately.
Any point in the right direction would be greatly appreciated.
Question#1: Getting accurate mouse position after the canvas has moved
Whenever you move your canvas (fex: margin: 0 auto), you must recalculate your offsetX and offsetY values:
If you manually change the canvas element's CSS (fex: canvas.style.margin='50px' inside javascript), then you must also manually call reOffset().
// cache the canvas's offset positions since the
// offset positions are used often
var offsetX,offsetY;
// call this once at the beginning of your app
// and whenever you change the canvas's position on the page
// (eg call when you change margins, scroll, etc)
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
// have the browser auto-reset offsetX & offsetX when
// the viewport scrolls or resizes
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
Question#2 Detecting hovers & blurs over your polygons
Your hitTest function will test if the mouse is currently inside a specified polygon. So inside handleMousemove you could call hitText for each of the polygons inside your polyArray.
Keep a flag variable indicating the index# of the last polygon the mouse was inside (or -1 to indicate the mouse was outside all polygons. When your flag variable value changes, you know there has been either a hover-event or a blur-event. Compare the last and current flag variables to determine which polygon is now hovered or blurred.
Question#3 Incorporating a responsive design
Mouse coordinates reported by the browser into e.clientX and e.clientY are always in unscaled values relative to the browser viewport.
So if you:
Click the mouse and use e.clientX/e.clientY to determine the mouse is at [100,100],
Scale your canvas: context.scale(2,2),
And reclick without moving the mouse from its original [100,100] position,
Then:
Using e.clientX/e.clientY to detect the mouse coordinates will still report the position as [100,100] even if the canvas has been scaled and the mouse is at [200,200] relative to the scaled canvas.
The fix:
You must scale the browser's reported mouse position to match the scaling factor of the canvas:
// Determine how much you want to scale the canvas
var scaleFactor=2.00;
// scale the canvas
context.scale(scaleFactor,scaleFactor);
// also scale the mouse position reported by the browser
mouseX=parseInt(e.clientX-offsetX)*scaleFactor;
mouseY=parseInt(e.clientY-offsetY)*scaleFactor;

Get bounding client rectangle without borders

I made a function that transforms mouse coordinates to canvas pixel coordinates:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
//Recalculate mouse offsets to relative offsets
x = event.clientX - rect.left;
y = event.clientY - rect.top;
//Also recalculate offsets of canvas is stretched
var width = rect.right - rect.left;
//I use this to reduce number of calculations for images that have normal size
if(this.width!=width) {
var height = rect.bottom - rect.top;
//changes coordinates by ratio
x = x*(this.width/width);
y = y*(this.height/height);
}
//Return as an array
return [x,y];
}
You can see demonstration of the pixel coordinate calculation. The problem is that the solutions fails for images having border property set.
How can I subtract the border width from rectangle? Performance does matter, as this calculation is often performed during mouse move events.
getComputedStyle contains the information you desire:
Fetch the border information once at the beginning of your app after the canvas border has been set.
// get a reference to the canvas element
var canvas=document.getElementById('yourCanvasId');
// get its computed style
var styling=getComputedStyle(canvas,null);
// fetch the 4 border width values
var topBorder=styling.getPropertyValue('border-top-width');
var rightBorder=styling.getPropertyValue('border-right-width');
var bottomBorder=styling.getPropertyValue('border-bottom-width');
var leftBorder=styling.getPropertyValue('border-left-width');
If you scope these border-width variables app-wide, you can use these prefetched variables in your HTMLCanvasElement.prototype.relativeCoords.
Good luck with your project!

content scrolling on canvas via mousemovement

I'm trying to implement an infinite background scene scroll that is controlled by the mousemove on a canvas
I have the following in a requestAnimationFrame working almost right
y1 = y1 >= h? y1 - h : y1; // y1 is the current mouse position on('drag') less the offset if the position is greater than height of canvas reset with offset
c.font="20px Georgia";
c.clearRect(0,0, w, h); // w, h is the width and height of canvas
c.save();
//c.translate(0, y1);
for (var i = 0; i < numImages; i++) { // number of images to be drawn to fill area
var y = y1 + (i * im.height); // reposition the images relative to mouse position
y = y >= h? - im.height + y1 : y; // if the image position is off screen then re position to top relative to mouse
c.drawImage(im, 0, y, im.width, im.height);
}
c.fillText("y: " + y,10,190);
c.restore();
However the problem comes in when I have reached the end of the canvas edges and need to "loop" the image... can't figure out how to reposition the images after going out of view in relation to the mouse position
Any insight on this?
Update:
I'm able to get a continuous scroll but I believe its being messed up by an e.preventDefault on the document element while i'm running the 'touchmove' on the canvas element itself...
Is it safer to run the code on the canvas or the document?
Here's one way of creating an infinite panorama:
Create a horizontally mirrored image from your source image.
Create an animation loop that draws first the original & then the mirror image to the right of the original image..
Panning Visually Rightward: With each loop, offset the images by 1 pixel leftward (offset--). When the offset is the size of original+mirror width, then reset the offset to zero.
Panning Visually Leftward: Start with an offset=-(original+mirror width), With each loop, offset the images by 1 pixel rightward (offset++). When the offset is zero, then reset the offset to -(original+mirror).
Mouse Control: You can use the mouse-X position to determine to calculate your desired offset (which in turn determines your position within the infinite panorama).
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var infiniteImage;
var infiniteImageWidth;
var img=document.createElement("img");
img.onload=function(){
// use a tempCanvas to create a horizontal mirror image
// This makes the panning appear seamless when
// transitioning to a new image on the right
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=img.width*2;
tempCanvas.height=img.height;
tempCtx.drawImage(img,0,0);
tempCtx.save();
tempCtx.translate(tempCanvas.width,0);
tempCtx.scale(-1,1);
tempCtx.drawImage(img,0,0);
tempCtx.restore();
infiniteImageWidth=img.width*2;
infiniteImage=document.createElement("img");
infiniteImage.onload=function(){
pan();
}
infiniteImage.src=tempCanvas.toDataURL();
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/mountain.jpg";
var fps = 60;
var offsetLeft=0;
function pan() {
// increase the left offset
offsetLeft+=1;
if(offsetLeft>infiniteImageWidth){ offsetLeft=0; }
ctx.drawImage(infiniteImage,-offsetLeft,0);
ctx.drawImage(infiniteImage,infiniteImage.width-offsetLeft,0);
setTimeout(function() {
requestAnimationFrame(pan);
}, 1000 / fps);
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>Infinite panorama using mirror image</h4>
<canvas id="canvas" width=500 height=143></canvas>
Most simple way is to do two passes : one down, one up
y1 = y1 >= h? y1 - h : y1; // y1 is the current mouse position on('drag') less the offset if the position is greater than height of canvas reset with offset
c.font="20px Georgia";
c.clearRect(0,0, w, h); // w, h is the width and height of canvas
c.save();
var cumulatedHeight=0;
for (var i = 0; i < numImages; i++) { // number of images to be drawn to fill area
var y = y1 + cumulatedHeight; // reposition the images relative to mouse position
if (y>h) break;
c.drawImage(im, 0, y, im.width, im.height);
cumulatedHeight+= im.height;
}
cumulatedHeight=0;
for (var i = numImage-1; i >=0; i--) { // number of images to be drawn to fill area
cumulatedHeight+= im.height;
var y = y1 - cumulatedHeight; // reposition the images relative to mouse position
if (y+im.height<0) break;
c.drawImage(im, 0, y, im.width, im.height);
}
c.fillText("y: " + y,10,190);
c.restore();
(you might notice i started the work in case you want images with different heights : just add
var im = someImageArray[i];
in each for loop.)

Mouse position on canvas painting

The code below paints correctly but it paints to wrong coordinates. It should paint the place where the mouse is. I was not able to discover my mistake. Thanks.
JSFIDDLE
container.mousedown(function(e) {
var parentOffset = $(this).offset();
var x = e.pageX - parentOffset.left;
var y = e.pageY - parentOffset.top;
context_temp.beginPath();
context_temp.moveTo(x, y);
started = true;
});
container.mousemove(function(e) {
var parentOffset = $(this).offset();
var x = e.pageX - parentOffset.left;
var y = e.pageY - parentOffset.top;
if (started) {
context_temp.lineTo(x, y);
context_temp.stroke();
}
});
container.mouseup(function(e) {
var parentOffset = $(this).offset();
var x = e.pageX - parentOffset.left;
var y = e.pageY - parentOffset.top;
if (started) {
container.mousemove(x, y);
started = false;
update();
}
});
You're setting your canvas width and height in CSS. That just stretches the canvas the same as it would an image.
The effect is drawing in the wrong place.
Instead you need to set your canvas dimensions on the tag itself:
<canvas width="400" height="400"></canvas>
A <canvas> has its own width and height, which not only define its physical size (unless CSS steps in), but also its logical size (the number of rows/columns of pixels on its drawing surface). When CSS changes the size, the canvas stretches to fit, but doesn't change its logical size. Basically, the pixels stretch too, so the logical and physical coordinates no longer match up.
To fix the problem, you could either do the math to match the coordinates back up, or exclusively use the canvases' own width/height to size them, or set the canvases' width and height properties after the fact to match the width and height set by CSS.

Drawing polygons in canvas with the mouse: coord issue when the div is included in the design of the site

I'm working with this script "Drawing polygons with the mouse" and it works very well.
The issue I have is when I put the canvas in the design of my site. The canvas is thus now in relative position and the coords are wrong. I have a lag between my cursor and the draw…
If I set the div in position: fixed, there is no problem.
The positions are declared as follows:
canvas.addEventListener("click", function(e) {
var x = e.clientX-canvas.offsetLeft;
var y = e.clientY-canvas.offsetTop;
How to fix this? How to put the canvas in my design and have the right coords?
Thank you very much!
Try my "simple" mouse code (simple because it does not take into account border/padding/HTML offset):
function getMouse(e, canvas) {
var element = canvas, offsetX = 0, offsetY = 0, mx, my;
// Compute the total offset
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// This isn't the best code because I am not adding padding and border style widths to offset. I'm just keeping it simple.
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {x: mx, y: my};
}

Categories