For some reason the console keeps on saying Uncaught TypeError: Cannot read property 'clientX' of undefined
at moveCircle (script.js:5) but the code still works in the browser. Could you explain how that error is appearing in the console?
1 const CIRCLE = document.querySelector(".circle");
2 const BODY = document.body;
3
4 function moveCircle(e) {
5 CIRCLE.style.left = e.clientX + "px";
6 CIRCLE.style.top = e.clientY + "px";
7 }
8
9 BODY.addEventListener("mousemove", moveCircle, false);
10 setInterval(moveCircle, 1);
Function moveCircle called by setInterval doesn't have event object.
Function moveCircle triggered by event mousemove will work.
Why do you want call moveCircle by setInterval?
For these kind of things please use window requestAnimationFrame (rAF), since rAF or setTimeout cannot access event directly, your best choice is to either record the event somewhere and consume it from there, or store the last coordinates and operate on them. Something like this maybe??
https://jsfiddle.net/ibowankenobi/8z5a5sgj/5/
var circle = document.querySelector(".circle");
var pos = [0,0];
document.getElementById("container").addEventListener("mousemove",updatePos,false);
window.requestAnimationFrame(move);
function updatePos(e){
pos[0] = e.clientX || 0;
pos[1] = e.clientY || 0
}
function move(){
var diffX = pos[0] - circle.offsetLeft;
var diffY = pos[1] - circle.offsetTop;
if(diffX) {
circle.style.left = circle.offsetLeft+Math.max(-5,Math.min(diffX/10,5))+"px";
}
if(diffY) {
circle.style.top = circle.offsetTop+Math.max(-5,Math.min(diffY/10,5))+"px";
}
window.requestAnimationFrame(move);
}
Related
I can't get the mouse coordinates like this
function handleClick() {
getMouseCoordinates();
calculateDistance();
handleRotation();
document.getElementById("tester").innerHTML = mouseX;
}
function getMouseCoordinates(e) {
var offset = $('#gameWrapper').offset();
mouseX = Math.round(e.clientX - offset.left);
mouseY = Math.round(e.clientY - offset.top);
}
But this way it works
function handleClick(e) {
var offset = $('#gameWrapper').offset();
mouseX = Math.round(e.clientX - offset.left);
mouseY = Math.round(e.clientY - offset.top);
calculateDistance();
handleRotation();
document.getElementById("tester").innerHTML = mouseX;
}
The first way seems a better way to do it I think
You need to pass the event object into any of your functions that need it:
function handleClick(e) {
// ------------------^
getMouseCoordinates(e);
// ---------------------^
calculateDistance(); // If these need it, pass it...
handleRotation(); // ...to them as well
document.getElementById("tester").innerHTML = mouseX;
}
If you need information back from any of those (such as mouseX), make that information a return value from the function, and then save it in a local variable (or use it directly).
I am trying to use canvas so that with a mouse a person can write their signature. Everything works until I stretch or scroll the screen then it draws the line in a different place away from the mouse.
The Code:
function onMouseUp(event) {
'use strict';
mousePressed = false;
}
function onMouseMove(event) {
'use strict';
if (mousePressed) {
event.preventDefault();
mouseX = event.clientX - can.offsetLeft - mleft;
mouseY = event.clientY - can.offsetTop - mtop;
ctx.lineTo(mouseX, mouseY);
ctx.stroke();
}
}
function onMouseDown(event) {
'use strict';
mousePressed = true;
mouseX = event.clientX - can.offsetLeft - mleft;
mouseY = event.clientY - can.offsetTop - mtop;
ctx.beginPath();
ctx.moveTo(mouseX, mouseY);
}
can.addEventListener('mousemove', onMouseMove, false);
can.addEventListener('mousedown', onMouseDown, false);
can.addEventListener('mouseup', onMouseUp, false);
HTML looks like:
<canvas id="signature" width="567" height="150"></canvas>
event.clientX/Y is relative to the top left corner of the viewport. So scroll isn't taken into account. event.pageX/Y is relative to the document. So it is the position on screen that the event happened including scroll. You can change all references to clientX to pageX and clientY to pageY and it should work.
Explanation of each screen/page/client XY.
It seems like you just need a more reliable method of getting the relative coordinates when the page reflows.
#RyanArtecona wrote the following function in response to this question about mouse coordinates relative to a canvas:
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
This is convenient because it adds a function to get the relative coordinates right onto the prototype of the HTMLCanvasElement function, which means you can just pass in a reference to the canvas you want to use, and get coordinates relative to it.
Using this you could rewrite your mousedown function (and you'll want to do the others as well, but just for an example) as follows:
function onMouseDown(event) {
'use strict';
mousePressed = true;
// get a reference to the 'signature' canvas
var canvas = document.getElementById('signature');
// this returns an object with 'x' and 'y' properties
var mouse = canvas.relMouseCoords(event)
ctx.beginPath();
// use the coordinates you got
ctx.moveTo(mouse.x, mouse.y);
}
Change these two lines
mouseX = event.clientX - can.offsetLeft - mleft;
mouseY = event.clientY - can.offsetTop - mtop;
to
mouseX = event.offsetX || event.layerX;
mouseY = event.offsetY || event.layerY;
in both of your handlers. The browser can handle the relative coordinates for you without having to do any special math. offsetX/Y seems Chrome/IE specific and layerX/Y gets you Firefox support.
Here is a jsfiddle. I made a couple slight declaration changes to get your use stricts to work, since we seem to be missing a little of the code.
Add the scroll offset of the screen to the mouseX and mouseY. with jQuery it would look like this
mouseX = event.clientX - can.offsetLeft - mleft + $(window).scrollLeft;
mouseY = event.clientY - can.offsetTop - mtop + $(window).scrollTop;
This is not a simple answer. The best way to figure this out is to start with a good foundation and then make incremental changes for your situation.. The best article I have seen on this subject is from the Internet Explorer Team:
Handling Multi-touch and Mouse Input in All Browsers
This article will give you a great foundation to capturing mouse input correctly.
This is a followup question to How to zoom to mouse pointer while using my own mousewheel smoothscroll?
I am using css transforms to zoom an image to the mouse pointer. I am also using my own smooth scroll algorithm to interpolate and provide momentum to the mousewheel.
With Bali Balo's help in my previous question I have managed to get 90% of the way there.
You can now zoom the image all the way in to the mouse pointer while still having smooth scrolling as the following JSFiddle illustrates:
http://jsfiddle.net/qGGwx/7/
However, the functionality is broken when the mouse pointer is moved.
To further clarify, If I zoom in one notch on the mousewheel the image is zoomed around the correct position. This behavior continues for every notch I zoom in on the mousewheel, completely as intended. If however, after zooming part way in, I move the mouse to a different position, the functionality breaks and I have to zoom out completely in order to change the zoom position.
The intended behavior is for any changes in mouse position during the zooming process to be correctly reflected in the zoomed image.
The two main functions that control the current behavior are as follows:
self.container.on('mousewheel', function (e, delta) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
This function collects the current position of the mouse at the current scale of the zoomed image.
It then starts my smooth scroll algorithm which results in the next function being called for every interpolation:
zoom: function (scale) {
var self = this;
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
var compat = ['-moz-', '-webkit-', '-o-', '-ms-', ''];
var newCss = {};
for (var i = compat.length - 1; i; i--) {
newCss[compat[i] + 'transform'] = 'scale(' + scale + ')';
newCss[compat[i] + 'transform-origin'] = self.currentLocation.x + 'px ' + self.currentLocation.y + 'px';
}
self.image.css(newCss);
self.currentscale = scale;
},
This function takes the scale amount (1-10) and applies the css transforms, repositioning the image using transform-origin.
Although this works perfectly for a stationary mouse position chosen when the image is completely zoomed out; as stated above it breaks when the mouse cursor is moved after a partial zoom.
Huge thanks in advance to anyone who can help.
Actually, not too complicated. You just need to separate the mouse location updating logic from the zoom updating logic. Check out my fiddle:
http://jsfiddle.net/qGGwx/41/
All I have done here is add a 'mousemove' listener on the container, and put the self.mouseLocation updating logic in there. Since it is no longer required, I also took out the mouseLocation updating logic from the 'mousewheel' handler. The animation code stays the same, as does the decision of when to start/stop the animation loop.
here's the code:
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
});
Before you check this fiddle out; I should mention:
First of all, within your .zoom() method; you shouldn't divide by currentscale:
self.currentLocation.x += ((self.mouseLocation.x - self.currentLocation.x) / self.currentscale);
self.currentLocation.y += ((self.mouseLocation.y - self.currentLocation.y) / self.currentscale);
because; you already use that factor when calculating the mouseLocation inside the initmousewheel() method like this:
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
So instead; (in the .zoom() method), you should:
self.currentLocation.x += (self.mouseLocation.x - self.currentLocation.x);
self.currentLocation.y += (self.mouseLocation.y - self.currentLocation.y);
But (for example) a += b - a will always produce b so the code above equals to:
self.currentLocation.x = self.mouseLocation.x;
self.currentLocation.y = self.mouseLocation.y;
in short:
self.currentLocation = self.mouseLocation;
Then, it seems you don't even need self.currentLocation. (2 variables for the same value). So why not use mouseLocation variable in the line where you set the transform-origin instead and get rid of currentLocation variable?
newCss[compat[i] + 'transform-origin'] = self.mouseLocation.x + 'px ' + self.mouseLocation.y + 'px';
Secondly, you should include a mousemove event listener within the initmousewheel() method (just like other devs here suggest) but it should update the transform continuously, not just when the user wheels. Otherwise the tip of the pointer will never catch up while you're zooming out on "any" random point.
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
So; you wouldn't need to calculate this anymore within the mousewheel event handler so, your initmousewheel() method would look like this:
initmousewheel: function () {
var self = this;
self.container.on('mousewheel', function (e, delta) {
if (!self.running) {
self.running = true;
self.animateLoop();
}
self.delta = delta;
self.smoothWheel(delta);
return false;
});
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale); // <--- update transform origin dynamically
});
}
One Issue:
This solution works as expected but with a small issue. When the user moves the mouse in regular or fast speed; the mousemove event seems to miss the final position (tested in Chrome). So the zooming will be a little off the pointer location. Otherwise, when you move the mouse slowly, it gets the exact point. It should be easy to workaround this though.
Other Notes and Suggestions:
You have a duplicate property (prevscale).
I suggest you always use JSLint or JSHint (which is available on
jsFiddle too) to validate your code.
I highly suggest you to use closures (often refered to as Immediately Invoked Function Expression (IIFE)) to avoid the global scope when possible; and hide your internal/private properties and methods.
Add a mousemover method and call it in the init method:
mousemover: function() {
var self = this;
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
self.mouseLocation.x = (e.pageX - offset.left) / self.currentscale;
self.mouseLocation.y = (e.pageY - offset.top) / self.currentscale;
self.zoom(self.currentscale);
});
},
Fiddle: http://jsfiddle.net/powtac/qGGwx/34/
Zoom point is not exactly right because of scaling of an image (0.9 in ratio). In fact mouse are pointing in particular point in container but we scale image. See this fiddle http://jsfiddle.net/qGGwx/99/ I add marker with position equal to transform-origin. As you can see if image size is equal to container size there is no issue. You need this scaling? Maybe you can add second container? In fiddle I also added condition in mousemove
if(self.running && self.currentscale>1 && self.currentscale != self.lastscale) return;
That is preventing from moving image during zooming but also create an issue. You can't change zooming point if zoom is still running.
Extending #jordancpaul's answer I have added a constant mouse_coord_weight which gets multiplied to delta of the mouse coordinates. This is aimed at making the zoom transition less responsive to the change in mouse coordinates. Check it out http://jsfiddle.net/7dWrw/
I have rewritten the onmousemove event hander as:
self.container.on('mousemove', function (e) {
var offset = self.image.offset();
console.log(offset);
var x = (e.pageX - offset.left) / self.currentscale,
y = (e.pageY - offset.top) / self.currentscale;
if(self.running) {
self.mouseLocation.x += (x - self.mouseLocation.x) * self.mouse_coord_weight;
self.mouseLocation.y += (y - self.mouseLocation.y) * self.mouse_coord_weight;
} else {
self.mouseLocation.x = x;
self.mouseLocation.y = y;
}
});
I've got some very simple code that attempts to attach an event listener and call a function on mousemove, so that I can find the mouse position within a canvas element:
canvas = document.getElementsByTagName('canvas');
canvas.addEventListener('mousemove', on_canvas_move, false);
function on_canvas_move(ev) {
var x = ev.clientX - canvas.offsetLeft;
var y = ev.clientY - canvas.offsetTop;
$('#status').html(x +', '+ y);
}
However I get the error: Uncaught TypeError: Object # has no method 'addEventListener'
What exactly is going on here?
getElementsByTagName() returns a nodeList (like an array of DOM objects) so you need to designate which element in that nodeList you want to add the listener to.
And secondly, your event handler on_canvas_move() also has the same issue as canvas.offsetLeft would be trying to read the .offsetLeft property on the nodeList, not on the DOM element. That would give you an undefined value and trying to do math with undefined gives you NaN.
To add just one:
canvas = document.getElementsByTagName('canvas');
canvas[0].addEventListener('mousemove', on_canvas_move, false);
function on_canvas_move(ev) {
var x = ev.clientX - this.offsetLeft;
var y = ev.clientY - this.offsetTop;
$('#status').html(x +', '+ y);
}
Or, to add all of them:
canvas = document.getElementsByTagName('canvas');
for (var i = 0; i < canvas.length; i++) {
canvas[i].addEventListener('mousemove', on_canvas_move, false);
}
function on_canvas_move(ev) {
var x = ev.clientX - this.offsetLeft;
var y = ev.clientY - this.offsetTop;
$('#status').html(x +', '+ y);
}
document.getElementsByTagName() returns you an array - you should either loop it and bind the listener, or point to the element you need:
document.getElementsByTagName('canvas')[0].addEventListener( ... )
An advice - when you don't know what's going on, figure it yourself by printing out stuff to console - console.log(canvas) would have told you.
This question already has answers here:
How do I get the coordinates of a mouse click on a canvas element? [duplicate]
(22 answers)
Closed 3 years ago.
Is there a way to get the location mouse inside a <canvas> tag? I want the location relative to to the upper right corner of the <canvas>, not the entire page.
The accepted answer will not work every time. If you don't use relative position the attributes offsetX and offsetY can be misleading.
You should use the function: canvas.getBoundingClientRect() from the canvas API.
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
console.log('Mouse position: ' + mousePos.x + ',' + mousePos.y);
}, false);
Easiest way is probably to add a onmousemove event listener to the canvas element, and then you can get the coordinates relative to the canvas from the event itself.
This is trivial to accomplish if you only need to support specific browsers, but there are differences between f.ex. Opera and Firefox.
Something like this should work for those two:
function mouseMove(e)
{
var mouseX, mouseY;
if(e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
else if(e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
/* do something with mouseX/mouseY */
}
Also note that you'll need CSS:
position: relative;
set to your canvas tag, in order to get the relative mouse position inside the canvas.
And the offset changes if there's a border
I'll share the most bulletproof mouse code that I have created thus far. It works on all browsers will all manner of padding, margin, border, and add-ons (like the stumbleupon top bar)
// Creates an object with x and y defined,
// set to the mouse position relative to the state's canvas
// If you wanna be super-correct this can be tricky,
// we have to worry about padding and borders
// takes an event and a reference to the canvas
function getMouse = function(e, canvas) {
var element = canvas, offsetX = 0, offsetY = 0, mx, my;
// Compute the total offset. It's possible to cache this if you want
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
// Also add the <html> offsets in case there's a position:fixed bar (like the stumbleupon bar)
// This part is not strictly necessary, it depends on your styling
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
// We return a simple javascript object with x and y defined
return {x: mx, y: my};
}
You'll notice that I use some (optional) variables that are undefined in the function. They are:
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
// Some pages have fixed-position bars (like the stumbleupon bar) at the top or left of the page
// They will mess up mouse coordinates and this fixes that
var html = document.body.parentNode;
htmlTop = html.offsetTop;
htmlLeft = html.offsetLeft;
I'd recommend only computing those once, which is why they are not in the getMouse function.
For mouse position, I usually use jQuery since it normalizes some of the event attributes.
function getPosition(e) {
//this section is from http://www.quirksmode.org/js/events_properties.html
var targ;
if (!e)
e = window.event;
if (e.target)
targ = e.target;
else if (e.srcElement)
targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
// jQuery normalizes the pageX and pageY
// pageX,Y are the mouse positions relative to the document
// offset() returns the position of the element relative to the document
var x = e.pageX - $(targ).offset().left;
var y = e.pageY - $(targ).offset().top;
return {"x": x, "y": y};
};
// now just make sure you use this with jQuery
// obviously you can use other events other than click
$(elm).click(function(event) {
// jQuery would normalize the event
position = getPosition(event);
//now you can use the x and y positions
alert("X: " + position.x + " Y: " + position.y);
});
This works for me in all the browsers.
EDIT:
I copied the code from one of my classes I was using, so the jQuery call to this.canvas was wrong. The updated function figures out which DOM element (targ) caused the event and then uses that element's offset to figure out the correct position.
GEE is an endlessly helpful library for smoothing out troubles with canvas, including mouse location.
Simple approach using mouse event and canvas properties:
JSFiddle demo of functionality http://jsfiddle.net/Dwqy7/5/
(Note: borders are not accounted for, resulting in off-by-one):
Add a mouse event to your canvas
canvas.addEventListener("mousemove", mouseMoved);
Adjust event.clientX and event.clientY based on:
canvas.offsetLeft
window.pageXOffset
window.pageYOffset
canvas.offsetTop
Thus:
canvasMouseX = event.clientX - (canvas.offsetLeft - window.pageXOffset);
canvasMouseY = event.clientY - (canvas.offsetTop - window.pageYOffset);
The original question asked for coordinates from the upper right (second function).
These functions will need to be within a scope where they can access the canvas element.
0,0 at upper left:
function mouseMoved(event){
var canvasMouseX = event.clientX - (canvas.offsetLeft - window.pageXOffset);
var canvasMouseY = event.clientY - (canvas.offsetTop - window.pageYOffset);
}
0,0 at upper right:
function mouseMoved(event){
var canvasMouseX = canvas.width - (event.clientX - canvas.offsetLeft)- window.pageXOffset;
var canvasMouseY = event.clientY - (canvas.offsetTop - window.pageYOffset);
}
I'd use jQuery.
$(document).ready(function() {
$("#canvas_id").bind( "mousedown", function(e){ canvasClick(e); } );
}
function canvasClick( e ){
var x = e.offsetX;
var y = e.offsetY;
}
This way your canvas can be anywhere on your page, relative or absolute.
Subtract the X and Y offsets of the canvas DOM element from the mouse position to get the local position inside the canvas.