Mouse position on canvas painting - javascript

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.

Related

Canvas grid gets blurry at different zoom levels

I am trying to create a simple canvas grid which will fit itself to the player's current zoom level, but also to a certain canvas height/width proportional screen limit. Here is what I got so far:
JS:
var bw = window.innerWidth / 2; //canvas size before padding
var bh = window.innerHeight / 1.3; //canvas size before padding
//padding around grid, h and w
var pW = 30;
var pH = 2;
var lLimit = 0; //9 line limit for both height and width to create 8x8
//size of canvas - it will consist the padding around the grid from all sides + the grid itself. it's a total sum
var cw = bw + pW;
var ch = bh + pH;
var canvas = $('<canvas/>').attr({width: cw, height: ch}).appendTo('body');
var context = canvas.get(0).getContext("2d");
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) { //handling the height grid
context.moveTo(x, 0);
context.lineTo(x, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) { //handling the width grid
context.moveTo(0, x); //begin the line at this cord
context.lineTo(bw, x); //end the line at this cord
lLimit++;
}
//context.lineWidth = 0.5; what should I put here?
context.strokeStyle = "black";
context.stroke();
}
drawBoard();
Now, I succeeded at making the canvas to be at the same proportional level for each screen resolution zoom level. this is part of what I am trying to achieve. I also try to achieve thin lines, which will look the same at all different zooming levels, and of course to remove the blurriness. right now the thickness
of the lines change according to the zooming levels and are sometimes blurry.
Here is jsFiddle (although the jsFiddle window itself is small so you will barely notice the difference):
https://jsfiddle.net/wL60jo5n/
Help will be greatly appreciated.
To prevent blur, you should account for window.devicePixelRatio when setting dimensions of your canvas element (and account for that dimensions during subsequent drawing, of course).
width and height properties of your canvas element should contain values that are proportionally higher than values in CSS properties of the same names. This can be expressed e.g. as the following function:
function setCanvasSize(canvas, width, height) {
var ratio = window.devicePixelRatio,
style = canvas.style;
style.width = '' + (width / ratio) + 'px';
style.height = '' + (height / ratio) + 'px';
canvas.width = width;
canvas.height = height;
}
To remove blurry effect on canvas zoom/scale i used image-rendering: pixelated in css
The problem is that you are using decimal values to draw. Both the canvas width and the position increments in your drawBoard() loop use fractions. The canvas is a bitmap surface, not a vectorial drawing. When you set the width and height of the canvas, you set the actual number of pixels stored in memory. That value cannot be decimal (browsers will probably just trim the decimal part). When you try to draw at decimal positions, the canvas will use pixel interpolation to avoid aliasing, hence the occasional blur.
See a version where I round x before drawing:
https://jsfiddle.net/hts7yybm/
Try rounding the values just before you draw them, but not in your actual logic. That way, the imprecision won't stack as the algorithm keeps adding to the value.
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) {
var roundedX = Math.round(x);
context.moveTo(roundedX, 0);
context.lineTo(roundedX, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) {
var roundedX = Math.round(x);
context.moveTo(0, roundedX);
context.lineTo(bw, roundedX);
lLimit++;
}
context.lineWidth = 1; // never use decimals
context.strokeStyle = "black";
context.stroke();
}
EDIT: I'm pretty sure all browsers behave as if the canvas was an img element, so there's no way to prevent aliasing when the user zooms with their browser's zoom function, other than with prefixed css. And even then, I'm not sure the browsers's zoom feature takes that into account.
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
Also, make sure the canvas doesn't have any CSS-set dimensions. That only stretches the image after it's been drawn instead of increasing the drawing surface. If you want to fill a block with the canvas by giving it 100% width and height, then you need some JS to compute the CSS-given height and width and set the value of the canvas's width and height property based on that. Then you can make your own implementation of a zoom function within your canvas drawing code, but depending on what you're doing it might be overkill.

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.)

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};
}

Canvas accessing coordinate of translated position of panning background image

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.

Categories