Correct Mouseposition on element while using CSS aspect ratio - javascript

I have a canvas that scales to the 16:9 aspect ratio. However the mouse does not take the offset (black background) into account.
The dotted green line marks the canvas. At the edge the mouse should display: Mouse : 0% | 23% rather than Mouse : 7% | 23%.
The problem seems to occur here:
var x = Math.floor( mouse.x / canvas.width * 100 );
var y = Math.floor( mouse.y / canvas.height * 100 );
It works fine when I go into fullscreen mode. (because there is no offset) But otherwise it does not take the offset into account.
Additional Code:
CSS:
#aspectoverlay {
/* 16:9 */
width: 100vw;
height: 56.25vw; /* 100/56.25 = 1.778 */
border: 1px #AFFF00 dashed;
max-height: 100vh;
max-width: 177.78vh; /* 16/9 = 1.778 */
margin: auto;
position: absolute;
top:0;bottom:0; /* vertical center */
left:0;right:0; /* horizontal center */
z-index: 100;
opacity: 1;
}
JS:
var canvas = document.getElementById('aspectoverlay');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext('2d');
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
canvas.addEventListener('mousemove', function(e){
mouse.x = e.x;
mouse.y = e.y;
});
var x = Math.floor( mouse.x / canvas.width * 100 );
var y = Math.floor( mouse.y / canvas.height * 100 );
ctx.fillText("Mouse : " + x + "% | " + y + "%", 50,50);

You just need to take the offsets of the element into account like so
mouse.x = e.x - this.offsetLeft;
mouse.y = e.y - this.offsetTop;
And since you are using CSS to resize and setting the canvas to the innerHeight and Width, you can't use the canvas width and height for calculations anymore you need to use offsetWidth and offsetHeight
var x = Math.floor(mouse.x / canvas.offsetWidth * 100);
var y = Math.floor(mouse.y / canvas.offsetHeight * 100);
Live Demo
Full Screen
var canvas = document.getElementById('aspectoverlay');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext('2d'),
mouse = {
x: 0,
y: 0
};
ctx.fillStyle = "#fff";
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle = "#fff";
}
canvas.addEventListener('mousemove', function (e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
mouse.x = e.x - this.offsetLeft;
mouse.y = e.y - this.offsetTop;
var x = Math.floor(mouse.x / canvas.offsetWidth * 100);
var y = Math.floor(mouse.y / canvas.offsetHeight * 100);
ctx.fillText("Mouse : " + x + "% | " + y + "%", 50, 50);
});

Related

Pan around the canvas within bounds smoothly

Most other solutions I have found relate to images as the bounds and work through css, and I can't get them to work with a canvas.
I want to create the bounds by defining an arbitrary percent size of the canvas (e.g 2x - twice the size) that expands from the center of the canvas and then have the canvas view (red) move/pan around within the bounds (green) by mouse hovering inside the canvas - that it moves to all corners of the bounds from the center of the canvas.
like this behavior in terms of the direction with panning
http://jsfiddle.net/georgedyer/XWnUA/2/
Additionally, I would like
the solution to work with a responsive canvas sizes rather than fixed canvas sizes
the panning to be smooth using easing such as CubicInOut easing
to detect the mouse hover inside the circle drawn. Might have to change the coordinate from screen to world to get it to work properly. But maybe theirs an easier way?
formulas
Here's my attempt at figuring out the formulas to get it working! I'll just focus on just one axis (x-axis) from left to right of the mouse, but the same formulas should apply to the y-axis.
First I get the "percentX" of the mouse along the width. If the "percentX" is 0 then the "newX" for the pan will be the value of the leftSide, else if it's 1 then it'll be the rightSide of the boundsBorder (green). So the "percentX" determines which side of the boundsBorder/limit the canvas view will extend/move to.
Only problem is that this doesn't work. So my formula must be wrong. The mouse inside the circle event is also inaccurate because of my canvas translate. Combining everything together is difficult to figure out!
Code
<html>
<head>
<meta charset="UTF-8">
<title>Panning</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
body {
background: #eee;
padding: 20px 0;
}
.canvas-container {
height: 400px;
width: 100%;
margin-bottom: 40px;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
canvas {
width: 100%;
height: inherit;
}
.container {
width: 60%;
margin: 0 auto;
}
</style>
</head>
<body>
<!-- CANVAS -->
<div class="container">
<div class="canvas-container">
<canvas id="canvas"></canvas>
</div>
</div>
<!-- SCRIPT -->
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width = canvas.clientWidth;
var height = canvas.height = canvas.clientHeight;
var panX = 0, panY = 0;
var scaleBorderFactor = 2; /* scale factor for the bounds */
var mouse = {
x: 0,
y: 0
}
function Circle(x, y, radius, color){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.draw = function(ctx){
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
/* Rect Stroke Borders for visual reference */
function RectBorder(x, y, w, h, c){
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.color = c;
this.draw = function(ctx){
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.strokeRect(this.x, this.y, this.width, this.height);
};
/* Draw rect from a center point */
this.drawAtCenter = function(ctx){
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.strokeRect(this.x, this.y, this.width, this.height);
};
this.toString = function(){
console.log("x: "+this.x+", y: "+this.y+", w: "+this.width+", h: "+this.height+", color = "+this.color);
}
}
function getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: (event.clientX - rect.left),
y: (event.clientY - rect.top)
};
}
function insideCircle(circle, mouse){
var dist = Math.sqrt(Math.pow(circle.x - mouse.x,2) + Math.pow(circle.y - mouse.y,2));
console.log(circle.radius+", d = "+dist)
return dist <= circle.radius;
}
function lerp(start, end, percent){
return start*(1 - percent) + percent*end;
}
/* t: current time, b: beginning value, c: change in value, d: duration */
function easeInOutCubic(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
var canvasBorder = new RectBorder(0,0,canvas.width,canvas.height,'red');
var boundsBorder = new RectBorder(-(canvas.width*scaleBorderFactor - canvas.width)/2,-(canvas.height*scaleBorderFactor - canvas.height)/2,canvas.width*scaleBorderFactor,canvas.height*scaleBorderFactor,'green');
var circle = new Circle(200,200,40,'blue');
/* Draw Update */
update();
function update(){
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
width = canvas.width = canvas.clientWidth;
height = canvas.height = canvas.clientHeight;
ctx.clearRect(0,0,width,height);
ctx.save();
/* MOUSE */
percentX = (mouse.x / width);
percentY = (mouse.y / height);
/* the 2 sides of boundsBorder */
var leftSide = (width*scaleBorderFactor - width)/2 - width;
var rightSide = (width*scaleBorderFactor - width)/2 + width;
var topSide = (height*scaleBorderFactor - height)/2 - width;
var bottomSide = (height*scaleBorderFactor - height)/2 + height;
newX = rightSide * percentX + leftSide * (1 - percentX);
newY = bottomSide * percentY + topSide * (1 - percentY);
/* maybe use easeInOutCubic for better smoothness */
panX = lerp(-newX, mouse.x, 0.1);
panY = lerp(-newY, mouse.y, 0.1);
if (insideCircle(circle, mouse)){
circle.color = "pink";
} else {
circle.color = "blue";
}
ctx.translate(panX,panY);
/* Draw both borders only for reference */
canvasBorder.draw(ctx);
boundsBorder.drawAtCenter(ctx);
/* Draw Circle */
circle.draw(ctx);
ctx.restore();
requestAnimationFrame(update);
}
/* Events */
function mousemove(e) {
mouse = getMousePos(canvas, e);
}
/* Event Listeners */
canvas.addEventListener('mousemove', mousemove);
</script>
</body>
</html>
EDIT
I have managed to get the pan functionality to work. I just realized that all I need to do is offset by the positive and negative "widthGap"(see picture below) amount when panning. So it goes from positive "widthGap" (moves to the rght) to negative "widthGap" (moves to the left) when the "percentX" changes value like mentioned previously.
To get the smooth movement, I instead applied the lerp to the "percentX" values.
The code below is what should be replaced from the previous code above. It also shows the new variables I defined, which will make some of the variables from the previous code redundant.
UPDATED Code
var canvasBorder = new RectBorder(0,0,canvas.width,canvas.height,'red');
/* widthGap is the new leftSide formula.
* it is the gap differance between border and canvas
*/
var widthGap = (canvas.width*scaleBorderFactor - canvas.width)/2;
var heightGap = (canvas.height*scaleBorderFactor - canvas.height)/2;
var boundsBorder = new RectBorder(-widthGap,-heightGap,canvas.width+widthGap*2,canvas.height+heightGap*2,'green');
var circle = new Circle(200,200,40,'blue');
/* Draw Update */
update();
var newPercentX = 0, newPercentY = 0;
var percentX = 0, percentY = 0;
function update(){
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
width = canvas.width = canvas.clientWidth;
height = canvas.height = canvas.clientHeight;
ctx.clearRect(0,0,width,height);
ctx.save();
newPercentX = (mouse.x / width);
newPercentY = (mouse.y / height);
/* MOUSE */
percentX = lerp(percentX,newPercentX,0.05);
percentY = lerp(percentY,newPercentY,0.05);
panX = (widthGap) * percentX + (-widthGap) * (1 - percentX);
panY = (heightGap) * percentY + (-heightGap) * (1 - percentY);
ctx.translate(-panX,-panY);
if (insideCircle(circle, mouse)){
circle.color = "pink";
} else {
circle.color = "blue";
}
/* Draw both borders only for reference */
canvasBorder.draw(ctx);
boundsBorder.drawAtCenter(ctx);
/* Draw Circle */
circle.draw(ctx);
ctx.restore();
requestAnimationFrame(update);
}

Draw cropped image in canvas

How to get the right position of image and draw what I see in lens into canvas?
Here is my example with a demo.
var isDown,
lens,
img,
canvas,
cx,
cy,
ctx,
image;
img = document.getElementById('img');
lens = document.getElementById('lens');
canvas = document.getElementById('canvas');
cx = canvas.offsetWidth / lens.offsetWidth;
cy = canvas.offsetHeight / lens.offsetHeight;
ctx = canvas.getContext('2d');
image = new Image();
image.src = img.src;
lens.addEventListener("mousemove", (e) => {
moveLens(e);
});
img.addEventListener("mousemove", (e) => {
moveLens(e);
});
function moveLens(e) {
var pos, x, y;
e.preventDefault();
pos = getCursorPos(e);
x = pos.x - (lens.offsetWidth / 2);
y = pos.y - (lens.offsetHeight / 2);
if (x > img.width - lens.offsetWidth) {
x = img.width - lens.offsetWidth;
}
if (x < 0) {
x = 0;
}
if (y > img.height - lens.offsetHeight) {
y = img.height - lens.offsetHeight;
}
if (y < 0) {
y = 0;
}
lens.style.left = x + "px";
lens.style.top = y + "px";
draw(x, y);
}
function getCursorPos(e) {
var a, x = 0,
y = 0;
e = e || window.event;
a = img.getBoundingClientRect();
x = e.pageX - a.left;
y = e.pageY - a.top;
x = x - window.pageXOffset;
y = y - window.pageYOffset;
return {
x: x,
y: y
};
}
function draw(x, y) {
ctx.drawImage(image, x, y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
}
#container {
position: relative;
width: 300px;
display:inline-block;
}
#container>img {
max-width: 100%;
}
#lens {
position: absolute;
border: 1px solid #d4d4d4;
width: 80px;
height: 80px;
}
<div id="container">
<div id="lens"></div>
<img src="https://cdn.pixabay.com/photo/2016/06/18/17/42/image-1465348_960_720.jpg" id="img">
</div>
<canvas width="200" height="200" id="canvas"></canvas>
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
The sx,sy,sWidth,sHeight are measured according to the source image. Say we have an image in way HIGH resolution, for instance, 2000 * 1000, then the sWidth and sHeight should be scaled up to that resolution. In your case, it should be 80 / 300 * 2000 for sWidth.
I've made those adjustments to your code.
function draw(x, y) {
ctx.clearRect(0, 0, canvas.width, canvas.height); // you might want to draw on a clean canvas each time
let scaleX = x / img.offsetWidth * image.width;
let scaleY = y / img.offsetHeight * image.height;
let scaleWidth = lens.offsetWidth / img.width * image.width;
let scaleHeight = lens.offsetHeight / img.height * image.height;
ctx.drawImage(image, scaleX, scaleY, scaleWidth, scaleHeight, 0, 0, canvas.width, canvas.height);
}

How to get Mouse Position on Transformed HTML5 Canvas

I am trying to get the mouse position on a transformed canvas. Here is my resize method:
window.addEventListener('resize', resize);
function resize() {
screenWidth = window.innerWidth;
screenHeight = window.innerHeight;
scaleFillNative = MathMAX(screenWidth / maxScreenWidth, screenHeight / maxScreenHeight);
mainCanvas.width = screenWidth;
mainCanvas.height = screenHeight;
mainContext.setTransform(scaleFillNative, 0, 0, scaleFillNative, Math.floor((screenWidth - (maxScreenWidth * scaleFillNative)) / 2),
Math.floor((screenHeight - (maxScreenHeight * scaleFillNative)) / 2));
}
The maxScreenWidth and maxScreenHeight represents the native screen dimensions that the canvas should be transformed to.
The actual resizing works fine. The issue however is that I am trying to render a circle at the mouse position on the canvas. The mouse position is set as follows:
window.addEventListener('mousemove', gameInput, false);
var mouseX, mouseY;
function gameInput(e) {
e.preventDefault();
e.stopPropagation();
mouseX = e.clientX;
mouseY = e.clientY;
}
And it is then rendered like this:
renderCircle(mouseX / scaleFillNative, mouseY / scaleFillNative, 10);
The x position is rendered correctly. However when I resize the window so that the width is less than the height, it no longer renders at the correct x location. The y position is always offset.
I don't know exactly what you have tried so far, but for a basic mouse coordinate to transformed canvas (non skewed), you'll have to do
mouseX = (evt.clientX - canvas.offsetLeft - translateX) / scaleX;
mouseY = (evt.clientY - canvas.offsetTop - translateY) / scaleY;
But canvas.offsetXXX doesn't take scroll amount into account, so this demo uses getBoundingRect instead.
var ctx = canvas.getContext('2d');
window.addEventListener('resize', resize);
// you probably have these somewhere
var maxScreenWidth = 1800,
maxScreenHeight = 1200,
scaleFillNative, screenWidth, screenHeight;
// you need to set available to your mouse move listener
var translateX, translateY;
function resize() {
screenWidth = window.innerWidth;
screenHeight = window.innerHeight;
// here you set scaleX and scaleY to the same variable
scaleFillNative = Math.max(screenWidth / maxScreenWidth, screenHeight / maxScreenHeight);
canvas.width = screenWidth;
canvas.height = screenHeight;
// store these values
translateX = Math.floor((screenWidth - (maxScreenWidth * scaleFillNative)) / 2);
translateY = Math.floor((screenHeight - (maxScreenHeight * scaleFillNative)) / 2);
ctx.setTransform(scaleFillNative, 0, 0, scaleFillNative, translateX, translateY);
}
window.addEventListener('mousemove', mousemoveHandler, false);
function mousemoveHandler(e) {
// Note : I don't think there is any event default on mousemove, no need to prevent it
// normalize our event's coordinates to the canvas current transform
// here we use .getBoundingRect() instead of .offsetXXX
// because we also need to take scroll into account,
// in production, store it on debounced(resize + scroll) events.
var rect = canvas.getBoundingClientRect();
var mouseX = (e.clientX - rect.left - translateX) / scaleFillNative,
mouseY = (e.clientY - rect.top - translateY) / scaleFillNative;
ctx.fillRect(mouseX - 5, mouseY - 5, 10, 10);
}
// an initial call
resize();
<canvas id="canvas"></canvas>

Need help re-positioning a circle when the canvas size changes

So I'm using the function from this tutorial
http://www.html5rocks.com/en/tutorials/casestudies/gopherwoord-studios-resizing-html5-games/#disqus_thread
it looks like this -
function resizeGame() {
var gameArea = document.getElementById('canvas-container');
var widthToHeight = 11 / 6;
var newWidth = window.innerWidth;
var newHeight = window.innerHeight;
var newWidthToHeight = newWidth / newHeight;
if (newWidthToHeight > widthToHeight) {
newWidth = newHeight * widthToHeight;
gameArea.style.height = newHeight + 'px';
gameArea.style.width = newWidth + 'px';
} else {
newHeight = newWidth / widthToHeight;
gameArea.style.width = newWidth + 'px';
gameArea.style.height = newHeight + 'px';
}
gameArea.style.marginTop = (-newHeight / 2) + 'px';
gameArea.style.marginLeft = (-newWidth / 2) + 'px';
var gameCanvas = document.getElementById('ctx');
gameCanvas.width = newWidth;
gameCanvas.height = newHeight;
HEIGHT = newHeight;
WIDTH = newWidth
}
So everything that I use WIDTH and HEIGHT to position on the canvas work fine. But there's other things that I have in my game that just use an x and y and don't change with the canvas.
if(drawBall){
ctx.save();
ctx.beginPath();
ctx.arc(ball.x, ball.y, (ball.diameter/2) - 5,0, 2*Math.PI);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.restore();
}
What can I do to my ball.x and ball.y to make it so it correctly changes with the canvas?
It all comes down to ratio's. Knowing that you can always re position things when the screen re-sizes.
var ratio = { x:1, y: 1 };
function start(){
ratio.x = canvas.width / canvas.clientWidth;
ratio.y = canvas.height / canvas.clientHeight;
drawSomething();
}
function drawSomething(){
context.rect(rectX * ratio.x, rectY * ratio.y, rectWidth * ratio.x, rectHeight * ratio.y);
/// stroke and all that good stuff
}
now listen for window change
window.onresize = function(){
// get new ratio's and draw again
start();
}
With this it shouldn't matter what re-size the user does the canvas will draw at the same relative position and size.

KineticsJs auto resize canvas

My autoresize canvas not works with kineticjs, the console say me that canvas.width and canvas.height is not defined.
Any people could help me.
Thank you .
Put the function and the call down.
function resize() {
console.log("ddddddddddddd");
//stage.setWidth((window.innerWidth / 100) * 80);
var canvas = document.getElementById('Contenedor');
console.log(canvas, canvas.height, canvas.width);
if(window.innerWidth < 1280){
var canvasRatio = canvas.height / canvas.width;
console.log(canvasRatio);
var windowRatio = window.innerHeight / window.innerWidth;
console.log(windowRatio);
var width;
var height;
if (windowRatio < canvasRatio) {
height = window.innerHeight;
width = height / canvasRatio;
} else {
width = window.innerWidth;
height = width * canvasRatio;
}
canvas.style.width = (parseFloat(width)*0.75) + 'px';
canvas.style.height = (parseFloat(height)*0.75) + 'px';
}else{
canvas.style.width=987+'px';
canvas.style.height=544+'px';
}
};
window.addEventListener('resize', resize, false);
What you can do is set the height of the canvas with respect to the width.
And use stage.setScale() and stage.setSize() to dynamically change the size of the stage
function resize() {
var canvasWidthRatio = 0.6; // canvas width to viewport width ratio
var viewPortWidth = window.innerWidth;
var canvasWidth = viewPortWidth * canvasWidthRatio;
var canvasHeight = canvasWidth * 0.6,
scale = stage.getScale(),
stageWidth = stage.getWidth(),
stageHeight = stage.getHeight(),
scaleFactor = Math.min(canvasWidth / stageWidth, canvasHeight / stageHeight),
newScale = scale * scaleFactor;
stage.setScale(newScale);
stage.setSize(canvasWidth, canvasHeight);
stage.draw();
}
window.addEventListener('resize', resize, false);
I think kineticjs sets the canvas dimensions.
set the stage width and height and don't touch the canvas directly.
Hello and sorry my bad english, also thank you to help me. I work with the code of VijayB, but with his code I have problems with multiply the object scale with a float.. I realease a few changes and for me are working.
I put the code for someone that have my own problem.
function resize() {
var __canvasWidthRatio = 0.6;
console.log( __canvasWidthRatio) ; // canvas width to viewport width ratio
var __viewPortWidth = window.innerWidth;
console.log(window.innerWidth);
var __canvasWidth = __viewPortWidth * __canvasWidthRatio;
console.log(__canvasWidth);
var aspec = 769 / 544;
var __canvasHeight = aspec *__canvasWidth * __canvasWidthRatio;
var __scale = ui.stage.getScale();
var __stageWidth = ui.stage.getWidth();
var __stageHeight = ui.stage.getHeight();
var __scaleFactor = Math.min( __canvasWidth / __stageWidth, __canvasHeight / __stageHeight);
stage.escala= __scale.x *__scaleFactor;
ui.scale=__scale.x *__scaleFactor;
stage.setScale(stage.escala,stage.escala);
stage.setSize(__canvasWidth, __canvasHeight);
stage.draw();
}
window.addEventListener('resize', resize, false);

Categories

Resources