get mouse position inside resizing div using raycaster and vector - javascript

I am able to get the intersects from my click event when I use the window object to acquire height and width, but getting the intersects position on a canvas that's dynamically sized is proving much harder. I'm not certain of the formula I would need to use to calculate the vector.x and vector.y values with a div that isn't always the same size.
The canvas is the size of a div that always has a width: height ratio of 4:3 and resizes to fit in the window and is always positioned in the center of the window.
If I resize the window to be 4:3 then the following code works perfectly:
mouse.x = (ecx/div_width) *2 -1;
mouse.y= -(ecy/div_height) *2 + 1;
when I resize the window, whichever dimension is larger than the size of the canvas has the incorrect value. I've linked an image to roughly describe how the problem presents itself
Image of horizontal dimension issue
I initially thought that the matches would be as simple as dividing the difference between the the sizes of the window and the canvas by
My question is, how would I acquire the correct values to pass to the vector object for it's x and y attributes? (using Vector3 and Raycaster)
here is the function I'm using to try and get the object(s) being clicked:
function getClicked(event){
event.preventDefault();
var ecx = event.clientX;
var ecy = event.clientY;
//elem is the div containing the canvas
//the canvas is not the same size as the window
var elem_w = elem.innerWidth();
var elem_h = elem.innerHeight();
//most examples suggest using the window height and width
//to get the position of the mouse in the scene.
//since the scene isn't the same size as the window, that doesn't work
var ww = window.innerWidth;
var wh = window.innerHeight;
mouse.x = (ecx/ww) *2 -1;
mouse.y= -(ecy/wh) *2 + 1;
var objlist = []
rc.setFromCamera(mouse, camera);
var intersects = rc.intersectObjects(scene.children, true);
for (var i=0;i<names_to_spin.length;i++){
var obj = intersects[i];
objlist.push(obj);
}
//ideally, this should return a list of the objects under the cursor
return objlist;
}

Related

Touch events on canvas in javascript have wrong coordinates

I have an html5 canvas game for desktop browsers that uses the keyboard, and I'm trying to create compatibility for smartphones by adding touch-screen controls.
The current issue is that the events for touch input are returning X and Y coordinates that don't match the location where I'm touching.
The purple dots in the image above are a diagnostic test I added. The game places a circle in each location where it detects a touch event. In this case, I traced the border of my phone screen. The placement of dots illustrates the difference between where my screen actually is and where the game seems to think it is.
<body onload="html_init()">
<div class="canvas_div" id="canvas_div">
<canvas width="1050" height="600" id="canvas"></canvas>
</div>
</body>
function html_init()
{
canvas_html = document.getElementById ("canvas");
window.addEventListener ("resize", resize_canvas, false);
canvas_html.addEventListener ("touchstart", function(event) {touch_start (event)}, false);
}
function resize_canvas ()
{
// Scale to fit window, decide which scaling reaches edge first.
canvas_2d.restore();
canvas_2d.save();
var game_ratio = canvas_2d.canvas.width / canvas_2d.canvas.height;
var window_ratio = window.innerWidth / window.innerHeight;
var xratio = window.innerWidth / canvas_2d.canvas.width;
var yratio = window.innerHeight / canvas_2d.canvas.height;
var ratio;
if (game_ratio > window_ratio) ratio = xratio;
else ratio = yratio;
if (ratio > 1) ratio = 1;
canvas_2d.scale (ratio, ratio);
}
function touch_start (event)
{
var touch = event.changedTouches;
var x = touch[0].pageX;
var y = touch[0].pageY;
// this goes on to add the purple test sprite to the screen at that location.
}
I've tried referencing pageX, clientX and screenX, but none of them have the correct values. Even though my canvas is set to 1050 x 600, which is what my game runs at, the touch screen seems to think it's somewhere around 650 x 350. If it matters, the test phone is an iPhone 6.
I'm not sure what to check at this point. Any ideas are appreciated.
UPDATE: I've narrowed the problem down to two facts - that the canvas size is larger than the display area of the web page being shown, and that I'm using resize_canvas() to set the scale. It looks like I'm going to have to either reduce the overall size of the canvas to fit the phone or add a formulaic adjustment to the coordinates based on the scale ratio.
canvas_html.addEventListener ("touchstart", touch_start, false);
function resize_canvas ()
{
// Scale to fit window, decide which scaling reaches edge first.
canvas_2d.restore();
canvas_2d.save();
var current_ratio = canvas_2d.canvas.width / canvas_2d.canvas.height;
var new_ratio = window.innerWidth / window.innerHeight;
var xratio = window.innerWidth / canvas_2d.canvas.width;
var yratio = window.innerHeight / canvas_2d.canvas.height;
if (current_ratio > new_ratio) screen_size_ratio = xratio;
else screen_size_ratio = yratio;
if (screen_size_ratio > 1) screen_size_ratio = 1;
canvas_2d.scale (screen_size_ratio, screen_size_ratio);
}
function touch_start (event)
{
var touch = event.changedTouches;
var x = Math.floor (touch[0].clientX / screen_size_ratio);
var y = Math.floor (touch[0].clientY / screen_size_ratio);
}
Because of the scale() I was doing on the canvas, the actual coordinates had to be un-scaled using the ratio I calculated in canvas_resize().
I also had to change my "touchstart" event handler to not use an anonymous function. For whatever reason, this was causing the phone browser to default back to using "mousedown" for the click.

Moving dynamically created grouped object within contained area? (html5canvas - fabric.js)

What I'm trying to create is a small canvas widget that would allow a user to dynamically create a shape onto an image and then place it above an area that caught their interest, effectively it is a highlighter.
The problem is with adding a zoom function, as when I zoom onto the image I would like to ensure that;
There is no possible way for the dynamically created shape to be
dragged anywhere outside the image area. (completed - ish, relies on 2nd step)
You cannot drag the image out of the page view, the canvas area cannot show white space. Part of the image must always be shown, and fill the entire canvas area. (problem)
Here are two examples that I've drawn up, neither of which work correctly;
First example - getBoundingRect does not update and is bound to the image
Second example - getBoundingRect does update and is bound to the grouped object
From the link description you can see that I think I've narrowed the problem down, or at least noticed a key difference between the scripts with how the getBoundingRect behaves.
The first plunk seems to work fine, until you try to zoom in multiple times and at a greater zoom level, then it seems to start bugging out (may take a few clicks and a bit of messing around, it is very inconsistent). The second plunk is very jittery and doesn't work very well.
I've been stuck on this for a week or so now, and I'm at breaking point! So really hoping someone can point out what I'm doing wrong?
Code snippet below for first plunk;
// creates group
var objs = canvas.getObjects();
var group = new fabric.Group(objs, {
status: 'moving'
});
// sets grouped object position
var originalX = active.left,
originalY = active.top,
mouseX = evt.e.pageX,
mouseY = evt.e.pageY;
active.on('moving', function(evt) {
group.left += evt.e.pageX - mouseX;
group.top += evt.e.pageY - mouseY;
active.left = originalX;
active.top = originalY;
originalX = active.left;
originalY = active.top;
mouseX = evt.e.pageX;
mouseY = evt.e.pageY;
// sets boundary area for image when zoomed
// THIS IS THE PART THAT DOESN'T WORK
active.setCoords();
// SET BOUNDING RECT TO 'active'
var boundingRect = active.getBoundingRect();
var zoom = canvas.getZoom();
var viewportMatrix = canvas.viewportTransform;
// scales bounding rect when zoomed
boundingRect.top = (boundingRect.top - viewportMatrix[5]) / zoom;
boundingRect.left = (boundingRect.left - viewportMatrix[4]) / zoom;
boundingRect.width /= zoom;
boundingRect.height /= zoom;
var canvasHeight = canvas.height / zoom,
canvasWidth = canvas.width / zoom,
rTop = boundingRect.top + boundingRect.height,
rLeft = boundingRect.left + boundingRect.width;
// checks top left
if (rTop < canvasHeight || rLeft < canvasWidth) {
group.top = Math.max(group.top, canvasHeight - boundingRect.height);
group.left = Math.max(group.left, canvasWidth - boundingRect.width);
}
// checks bottom right
if (rTop > 0 || rLeft > 0) {
group.top = Math.min(group.top, canvas.height - boundingRect.height + active.top - boundingRect.top);
group.left = Math.min(group.left, canvas.width - boundingRect.width + active.left - boundingRect.left);
}
});
// deactivates all objects on mouseup
active.on('mouseup', function() {
active.off('moving');
canvas.deactivateAll().renderAll();
})
// sets group
canvas.setActiveGroup(group.setCoords()).renderAll();
}
EDIT:
I've added comments and tried to simplify the code in the plunks.
The relevant code starts within the if (active.id == "img") { code block.
I've put irrelevant code as functions at the bottom, they can largely be ignored. ( createNewRect() + preventRectFromLeaving() )
I've removed one of the plunks to avoid confusion.
Let me know if it helps, or If I should try to simplify further.
Thanks!
I think that the grouping was messing with the position of the background image. So, I tried removing the group when the image is moving and manually updating the position of the rect instead.
It sets the last position of the image before moving
var lastLeft = active.left,
lastTop = active.top;
And then it updates those and the position of the rect every time the image moves
rect.left += active.left - lastLeft;
rect.top += active.top - lastTop;
// I think this is needed so the rectangle can be re-selected
rect.setCoords();
lastLeft = active.left;
lastTop = active.top;
Since the image has to stay within the canvas, the rect stays inside the canvas, too, whenever the image moves. The rest of the code you wrote seemed to work fine.
http://plnkr.co/edit/6GGcUxGC7CjcyQzExMoK?p=preview

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!

Custom cursor image with javascript

I need to change my mouse cursor to a custom image.
If possible I would like to do it on a spritesheet.
I can't do it from the css because I'm using it in a game. I already know how to decide when etc.
What I need to know is how do I change the cursor to an image, and deciding the image position and size?
Is there any easy solution similar to the drawImage's image position?
You can set the CSS using javascript to hide the cursor:
your_canvas.style.cursor = "none"
You can then get the cursor's position (it's now hidden) with something like this:
your_canvas.addEventListener("mousemove", function (ev) {
var mouseX = ev.pageX - GetTopLeft(your_canvas).Left;
var mouseY = ev.pageX - GetTopLeft(your_canvas).Top;
});
Then you can modify your canvas to show your fancier cursor sprite at that location.
GetTopLeft is defined as follows:
function GetTopLeft(elm){
var x, y = 0;
//set x to elm’s offsetLeft
x = elm.offsetLeft;
//set y to elm’s offsetTop
y = elm.offsetTop;
//set elm to its offsetParent
elm = elm.offsetParent;
//use while loop to check if elm is null
// if not then add current elm’s offsetLeft to x
//offsetTop to y and set elm to its offsetParent
while(elm != null)
{
x = parseInt(x) + parseInt(elm.offsetLeft);
y = parseInt(y) + parseInt(elm.offsetTop);
elm = elm.offsetParent;
}
//here is interesting thing
//it return Object with two properties
//Top and Left
return {Top:y, Left: x};
}
Though I can't remember where I copied the GetTopLeft function from...
If you are using a canvas, just hide the cursor over the canvas and draw your own sprite on the canvas at mouse position.

Categories