Detecting mouse coordinates with precision - javascript

After a sleepness night I discovered something about this question which I think is fundamentally mind boggling, at least to me.
Mouse coordinates ARE NOT PRECISE (I guess at a high speed of processing where the whole canvas has to be recreated when movement occurs) as in my codes above. I have tested this code piece by piece and discovered that the problem is not in my loop, but in the precision of
if ((newMouseX !== mouseX) && (newMouseY !== mouseY)).
If you tested this part of code by slower times (which will allow your eyes to detect the difference in coordinates when 'it stops', then you will realise that newMouseX & mouseX are off by 1-2 pixel 90% of the time, > 2 pixel 9% of the time, and only equal about 1% of the time. (I did not measure the statistics but that is what I picked on several rounds of testing).
I can't get it to work in fiddle but I think you can copy it to your testing ground to see what I mean. If you can get it to work in fiddle it would be great so experts can give it a short :)
This means that the mouse is considered to be 'moving' by my code even when it should have 'stopped', and thus 'stops' several times in between, therefore calling the loop too many times in a second, which is the problem I have been having.
I would be happy to hear comments from other experts, including those who can test this and come up with a statistical precision/advice.
My advice, and solution for the moment, is to consider movement when the difference is more than 10 pixels of either coordinates. Of course this presents a problem, but I can leave with that until some better solution comes up.
so instead of
if ((newMouseX !== mouseX) && (newMouseY !== mouseY))
i have used
if (( Math.abs(newMouseX - mouseX) > 10) || ( Math.abs(newMouseY != mouseY) > 10) )
Another thing to consider is how to deal with the mouse position when it goes off my target canvas area... that looks like an infinite movement at the moment!
The Question:
How can I get the precise mouse coordinates so I can compare mouseX & newMouseX?
Thanks.

Mouse precision is determined by the hardware. A high-precision mouse will produce different results than a built-in mouse pad for instance (not to mention touch devices).
However, this is not the problem with your code and your scenario. You are only listening to the mousemove event. It will by definition only throw an event when you move the mouse - hence the new mouse position can never be at the same position as the previous one. That would be impossible and should be off by 100% (unless you are triggering two+ moves where the last goes back to the fist position before you check).
Normally one would listen to the mousedown and mouseup events as well as they are not dependent on a mouse move to trigger. Detecting start and stop solely based on mouse movement is considered impossible under all possible circumstances.
You can do a compromise and make a definition of what a start is and what a stop is, ie. if the mouse has not moved after x milliseconds it is considered a stop (start would be on first move).
This means you will need to follow this rule every time you need to detect a stop. Imaging doing a drawing and sometimes you draw sometime slow other times fast. Or, how do you move the mouse to a new position without drawing anything... There is a good reason for the mouse button(s) to be invented :-)
The rule will soon prove to be useless (or overly complicated prone to more than one error).
As to mouse positions outside canvas there are several ways to handle this.
You can get the canvas bounds by calling:
var canvasRect = canvas.getBoundingClientRect();
which gives you properties to check when mouse position is inside or outside this rectangle.
Another way is to listen to the mouseleave and mouseenter events on the canvas element.
A third is to actually use the mouse buttons. When mouse button is held down on the canvas element you set a flag so mousemove events are considered.
This will keep listening until the mouse button is released. If you release it outside canvas and is using the canvas mouseup event it won't be detected. Therefor you should listen to the window's mouseup event which will trigger in either case.
This also goes for mousemove events. Using the window event will allow you to record positions outside canvas. If you don't want to do this you can use canvas' mousemove which will clip at the canvas' boundaries.
It boils down to:
Use the mousedown, mousemove and mouseup events in combination and you'll be fine. All these events delivers clientX and clientY for mouse positions.
And if I may - you can also test by going to my easyCanvas project and run the sample:
Sample - mouse event details
This will show you details for mouse down, move and up (the details are extended with other information, but you can at least verify mouse positions - do a click without moving and you see the mouse position is exactly the same).

http://jsfiddle.net/6czap/74/
I hope i helped with this :)
$("div").mousemove(function(e){
var pageCoords = "( " + e.pageX + ", " + e.pageY + " )";
var clientCoords = "( " + e.clientX + ", " + e.clientY + " )";
$("span:first").text("( e.pageX, e.pageY ) : " + pageCoords);
$("span:last").text("( e.clientX, e.clientY ) : " + clientCoords);
});

Related

Tracking relative mouse position without a mousemove event

I need to track mouse position relative to a <canvas> element in my app. Currently, I have a mousemove event listener attached to the <canvas> that updates my mouse position whenever it fires, using offsetX/offsetY when available, or layerX/layerY when the offsetX/Y is not available. Using offsetX/Y or layerX/Y gives me mouse coordinates relative to my <canvas>, which is exactly what I want. As my app works its magic, various CSS 3d transformations get applied to the <canvas>, and even when <canvas> is very transformed, offsetX/Y still gives me accurate coordinates within the <canvas>'s local, transformed coordinate-space.
That's kind of confusing, so I'll try stating an example. If my <canvas> is 100px in both width and height, and is located at (0,0) relative to the browser viewport, and I click at (50,50) (in viewport coords), that corresponds to (50,50) in my <canvas>, and 50 is the value that is (correctly) returned via offsetX and offsetY. If I then apply transform: translate3d(20px,20px,0px) to my <canvas> and click at (50,50) (in viewport coords), since my canvas has been shifted 20px down and 20px to the right, that actually corresponds to (30,30) relative to the <canvas>, and 30 is the value that is (correctly) returned via offsetX and offsetY.
The problem I'm facing is what to do when the user is not physically moving the mouse, yet the <canvas> is being transformed. I'm only updating the position of the mouse on mousemove events, so what do I do when there is no mousemove?
For example. My mouse is positioned at (50,50) and no transformations are applied to the <canvas>. My this.mouseX and this.mouseY are both equal to 50; they were saved at the final mousemove event when I moved the mouse to (50,50). Without moving the mouse at all, I apply the above transformation (transform: translate3d(20px,20px,0px)) to my <canvas>. Now, I need this.mouseX and this.mouseY to each be equal to 30, as that is my mouse's new position relative to the current transformation of <canvas>. But this.mouseX and this.mouseY are still equal to 50. Since I never moved the mouse, there was no mousemove event fired, and those saved coords never got updated.
How can I deal with this? I thought about creating a new jQuery event, manually assigning some properties (pageX and pageY?) based on my old/previous mouse position, and then triggering that event, but I don't think that's going to cause the browser to recalculate the offsetX and offsetY properties. I've also been thinking about taking the known old/previous mouse position and multiplying it by my transformation matrix, but that's going to get real complicated since my mouse coordinates are in 2d-space, but the transformations I'm applying to <canvas> are all 3d transformations.
I guess really, what I want to do is take my known 2d page position and raycast it into the 3d space and find out where I'm hitting the transformed <canvas>, all in javascript (jQuery is available).
Is this possible? Does this even make sense?
Works in all browsers
var mouseX=0;
var mouseY=0;
var canvas = document.querySelector('#canvas');
var rect = canvas.getBoundingClientRect();
document.onmousemove = function(e) {
mouseX=e.clientX-rect.left;
mouseY=e.clientY-rect.top;
};
function updateCoords() {
mouseX=e.clientX-mouseX;
mouseY=e.clientY-mouseY;
setTimeout(updatecoords,10);
}
Now we can call updateCoords() function once to repeatedly check for new position.
updateCoords();
You can add your code inside the updateCoords() function and it will be executed each 10 milliseconds
Concept: mouseX and mouseY variables get updated on mousemove event, and also get updated when there is any change in the canvas position.
It looks like you want to refresh your mouseposition-values even if you don't move your mouse. You should try something like this:
var event = '';
var counter = 1;
$(function(e){
event = e;
window.setInterval(refresh, 10);
});
$(document).mousemove(function(e){
event = e;
refresh;
});
function refresh(){
counter++;
$('#mousepos').val("event.pageX: " + event.pageX + ", event.pageY: " + event.pageY + ", counter: " + counter)
}
The counter is just for visualisation of the refresh. You can set the interval to everything you want (10 = 10ms = 0.01s) Just move everything from your .mousemove() event into this refresh() function and call it properly and your mouse position should update even if you don't move your mouse.
Look at this fiddle for a life example: http://jsfiddle.net/82cmxw8L/1
EDIT:
Because my fiddle didn't work for the asker, i updated it: http://jsfiddle.net/82cmxw8L/8/
New is, that the mouseposition is now set every 0.1 Second, no matter what, rather than being updated only when the mouse moves.

No cursor movement when under Pointer Lock

I use Pointer Lock for capturing the cursor in a game I'm developing in JavaScript with three.js. I've done my basic Googling, but I can't seem to find why, on Chrome OS, the cursor doesn't seem to move. If you look at http://scheib.github.io/HTMLMisc/PointerLockAndFullscreen.html Pointer Locking is working just fine and there's even a nice "cursor" to go along with the movement!
JavaScript Console output (The logging is me):
I "fabricate" the movementX/Y with
if (evt.movementX === undefined) {
evt.movementX = evt.screenX - lastx;
evt.movementY = evt.screenY - lasty;
lastx = evt.screenX;
lasty = evt.screenY;
}
In case you're wondering, I'm just setting up basic graphics.
The cursor is not moving because you have "lock" it. But evt.movementX and evt.movementY gives you the direction of the movement.
You have to update the position or direction of your camera and render the result to make it feel like it moves.
Under pointer lock, only the movementX and movementY properties are available. All other "ordinary" position co-ordinates are deliberately set to zero.
In pointer lock mode, it is perceived that there is no cursor, only mouse movement, so there's no (screenX,screenY) for a cursor which does not exist, nor is there an (offsetX,offsetY) for the same reason.
Only (movementX,movementY) by design.

Is there any way to accelerate the mousemove event?

I wrote a little drawing script (canvas) for this website: http://scri.ch/
When you click on the document, every mousemove event basically executes the following:
- Get coordinates.
- context.lineTo() between this point and the previous one
- context.stroke() the line
As you can see, if you move the cursor very fast, the event isn’t triggering enough (depending on your CPU / Browser / etc.), and a straight line is traced.
In pseudocode:
window.addEventListener('mousemove', function(e){
myContext.lineTo(e.pageX, e.pageY);
myContext.stroke();
}, false);
This is a known problem, and the solution is fine, but I would like to optimize that.
So instead of stroke() each time a mousemove event is triggered, I put the new coordinates inside an array queue, and regularly draw / empty it with a timer.
In pseudocode:
var coordsQueue = [];
window.addEventListener('mousemove', function(e){
coordsQueue.push([e.pageX, e.pageY]);
}, false);
function drawLoop(){
window.setTimeout(function(){
var coords;
while (coords = coordsQueue.shift()) {
myContext.lineTo(coords[0], coords[1]);
}
myContext.stroke();
drawLoop();
}, 1000); // For testing purposes
}
But it did not improve the line. So I tried to only draw a point on mousemove. Same result: too much space between the points.
It made me realize that the first code block is efficient enough, it is just the mousemove event that is triggering too slowly.
So, after having myself spent some time to implement a useless optimization, it’s your turn: is there a way to optimize the mousemove triggering speed in DOM scripting?
Is it possible to “request” the mouse position at any time?
Thanks for your advices!
If you want to increase the reporting frequency, I'm afraid you're out of luck. Mice only report their position to the operating system n times per second, and I think n is usually less than 100. (If anyone can confirm this with actual specs, feel free to add them!)
So in order to get a smooth line, you'll have to come up with some sort of interpolation scheme. There's a whole lot of literature on the topic; I recommend monotone cubic interpolation because it's local, simple to implement, and very stable (no overshoot).
Then, once you've computed the spline, you can approximate it with line segments short enough so that it looks smooth, or you can go all-out and write your own Bresenham algorithm to draw it.
If all this is worth it for a simple drawing application... that's for you to decide, of course.
Cool site, unfortunately there is no way to request the current position of the mouse with JavaScript, the only hooks you have are the events you're already using. If you must have more control I'd look at using flash where you can change the frame rate and request the mouse position.
trace("Mouse X: " + _xmouse);
trace("Mouse Y: " + _ymouse);

Canvas: pointer based viewport / contain mouse pointer

There doesn't seem to be a good way to base the <canvas> viewport on the location of the mouse pointer and being able to move around freely. Basically, like every other first-person game on the market.
There is no way to capture the mouse
inside a <canvas> element.
There is no way to set the position
of the mouse pointer.
It is not possible to go full screen
with <canvas>, and even if, once the edge has been reached, functionality will be broken.
For good reasons, too. Imagine what possible scenarios could (and definitely would) be employed by malicious persons.
Perhaps it's too early to be thinking of something that is almost only of any use in a 3D environment, something that there isn't yet a spec for.
What's your take or solution?
You can get the mouse position inside of a canvas.
function getCursorPosition(e) {
var x;
var y;
if (e.pageX != undefined && e.pageY != undefined) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
var cell = new Cell(Math.floor(y/kPieceHeight),
Math.floor(x/kPieceWidth));
return cell;
}
From Dive Into HTML5: Let's Call it a Draw(ing Surface)
I don't think there is a good solution for this -- at least, not until we get mouse locking. No matter how elegant your solution, if you make a twitchy mouselook driven game, the user is going to twitch outside the canvas area at some point. Even if they don't accidentally click a link, their immersion will be broken when the view stops responding to their mouse.
For slower paced games, you could:
Use click and drag to turn. Once user starts dragging within the canvas, you use the mouse delta from the point where they started dragging to determine how far to turn. Because the user is holding down the button, they won't accidentally click things.
Hover cursor near the edges of the canvas to turn, similar to an RTS. This would turn more slowly, but is probably the most intuitive, and easiest for a user to accidentally discover.
Use the keyboard to look, like pre-mouse FPS games (such as Doom).
It's worth noting that there is an open feature request in Firefox for mouse locking. But, unfortunately, neither this, nor mouse hiding or fullscreen are part of the WebGL spec.
All of these features are supported by Unity, so that may be a path to look at if you really need FPS controls.

IE smoothing a map pan when dragging

I've trying to smooth out the performance of a map application i've been building using javascript. I initially implemented a dragging pan using
onmousedown
onmousemove
onmouseup
However in IE it feels really sluggish and it appears that when you move the cursor really fast the map doesn't update it position until you stop moving.
I converted my code to use the native IE events
ondragstart
ondrag
ondragend
The performance was MUCH better when using these events BUT it seems that I can't set the mouse cursor using the standard css properties. I can only set the cursor to a few predefined ones which is not what I want.
So my question is. How can I either smooth the drag in IE using the first set of events or how can I set a custom cursor using the native events.
EDIT: Code sample
The code is very very simple. Even when I remove the logic to load new tiles (i.e only the container is getting moved) it still feels clunky. Below is the pan function:
// the "this" object refers to the div containing all the tile layers.
function movemap(e)
{
e = e || window.event;
var dx = this.mouseX - e.clientX;
var dy = this.mouseY - e.clientY;
if (dx !== 0 || dy !== 0) {
this.style.left = parseInt(this.style.left) + dx + 'px';
this.style.top = parseInt(this.style.top) + dy + 'px';
this.mouseX = e.clientX;
this.mouseY = e.clientY;
}
}
It's really difficult to say, what makes your drag events to work slow, without code examples.
Sluggish effect can be due to some heavy operations you are running inside event execution. Event system in the browser is really interesting. It's synchronous. Which means, the event will be not triggered again, until the current execution is finished.
That means, you have two possibilities: 1. optimize your code inside event, so that it takes less CPU time or 2. make your event asynchronous and implement your own mutex/semaphor logic.
The second one you can do, by using setTimeout functionality. If you do setTimeout,((code),1) your event will continue asynchronously, so the next event will be dispatched without awaiting for your code to complete. Well, take in advance that in that case you have to start thinking about locking and queuing. By queuing I mean, to queue all dispatched events for future use.
Long time ago, I did also some asynchronous event dispatching, because of some heavy code execution. It worked for me. Maybe it will work for you too.

Categories

Resources