I want to make a little painting app using canvas. So I need to find the mouse's position on the canvas.
This question is related to
javascript
For those of you developing regular websites or PWAs (Progressive Web Apps) for mobile devices and/or laptops/monitors with touch screens, then you have landed here because you might be used to mouse events and are new to the sometimes painful experience of Touch events... yay!
There are just 3 rules:
mousemove
or touchmove
events.mousedown
or touchstart
events.Needless to say, things are more complicated with touch
events because there can be more than one and they're more flexible (complicated) than mouse events. I'm only going to cover a single touch here. Yes, I'm being lazy, but it's the most common type of touch, so there.
var posTop;_x000D_
var posLeft;_x000D_
function handleMouseDown(evt) {_x000D_
var e = evt || window.event; // Because Firefox, etc._x000D_
posTop = e.target.offsetTop;_x000D_
posLeft = e.target.offsetLeft;_x000D_
e.target.style.background = "red";_x000D_
// The statement above would be better handled by CSS_x000D_
// but it's just an example of a generic visible indicator._x000D_
}_x000D_
function handleMouseMove(evt) {_x000D_
var e = evt || window.event;_x000D_
var x = e.offsetX; // Wonderfully_x000D_
var y = e.offsetY; // Simple!_x000D_
e.target.innerHTML = "Mouse: " + x + ", " + y;_x000D_
if (posTop)_x000D_
e.target.innerHTML += "<br>" + (x + posLeft) + ", " + (y + posTop);_x000D_
}_x000D_
function handleMouseOut(evt) {_x000D_
var e = evt || window.event;_x000D_
e.target.innerHTML = "";_x000D_
}_x000D_
function handleMouseUp(evt) {_x000D_
var e = evt || window.event;_x000D_
e.target.style.background = "yellow";_x000D_
}_x000D_
function handleTouchStart(evt) {_x000D_
var e = evt || window.event;_x000D_
var rect = e.target.getBoundingClientRect();_x000D_
posTop = rect.top;_x000D_
posLeft = rect.left;_x000D_
e.target.style.background = "green";_x000D_
e.preventDefault(); // Unnecessary if using Vue.js_x000D_
e.stopPropagation(); // Same deal here_x000D_
}_x000D_
function handleTouchMove(evt) {_x000D_
var e = evt || window.event;_x000D_
var pageX = e.touches[0].clientX; // Touches are page-relative_x000D_
var pageY = e.touches[0].clientY; // not target-relative_x000D_
var x = pageX - posLeft;_x000D_
var y = pageY - posTop;_x000D_
e.target.innerHTML = "Touch: " + x + ", " + y;_x000D_
e.target.innerHTML += "<br>" + pageX + ", " + pageY;_x000D_
e.preventDefault();_x000D_
e.stopPropagation();_x000D_
}_x000D_
function handleTouchEnd(evt) {_x000D_
var e = evt || window.event;_x000D_
e.target.style.background = "yellow";_x000D_
// Yes, I'm being lazy and doing the same as mouseout here_x000D_
// but obviously you could do something different if needed._x000D_
e.preventDefault();_x000D_
e.stopPropagation();_x000D_
}
_x000D_
div {_x000D_
background: yellow;_x000D_
height: 100px;_x000D_
left: 50px;_x000D_
position: absolute;_x000D_
top: 80px;_x000D_
user-select: none; /* Disable text selection */_x000D_
-ms-user-select: none;_x000D_
width: 100px;_x000D_
}
_x000D_
<div _x000D_
onmousedown="handleMouseDown()" _x000D_
onmousemove="handleMouseMove()"_x000D_
onmouseout="handleMouseOut()"_x000D_
onmouseup="handleMouseUp()" _x000D_
ontouchstart="handleTouchStart()" _x000D_
ontouchmove="handleTouchMove()" _x000D_
ontouchend="handleTouchEnd()">_x000D_
</div>_x000D_
Move over box for coordinates relative to top left of box.<br>_x000D_
Hold mouse down or touch to change color.<br>_x000D_
Drag to turn on coordinates relative to top left of page.
_x000D_
Prefer using Vue.js? I do! Then your HTML would look like this:
<div @mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@touchstart.stop.prevent="handleTouchStart"
@touchmove.stop.prevent="handleTouchMove"
@touchend.stop.prevent="handleTouchEnd">
const findMousePositionRelativeToElement = (e) => {
const xClick = e.clientX - e.currentTarget.offsetLeft;
const yClick = e.clientY - e.currentTarget.offsetTop;
console.log(`x: ${xClick}`);
console.log(`y: ${yClick}`);
// or
const rect = e.currentTarget.getBoundingClientRect();
const xClick2 = e.clientX - rect.left;
const yClick2 = e.clientY - rect.top;
console.log(`x2: ${xClick2}`);
console.log(`y2: ${yClick2}`);
}
Taken from this tutorial, with corrections made thanks to the top comment:
function getMousePos( canvas, evt ) {
var rect = canvas.getBoundingClientRect();
return {
x: Math.floor( ( evt.clientX - rect.left ) / ( rect.right - rect.left ) * canvas.width ),
y: Math.floor( ( evt.clientY - rect.top ) / ( rect.bottom - rect.top ) * canvas.height )
};
}
Use on a canvas as follows:
var canvas = document.getElementById( 'myCanvas' );
canvas.addEventListener( 'mousemove', function( evt ) {
var mousePos = getMousePos( canvas, evt );
} );
You can simply use jQuery’s event.pageX and event.pageY with the method offset() of jQuery to get the position of the mouse relative to an element.
$(document).ready(function() {
$("#myDiv").mousemove(function(event){
var X = event.pageX - $(this).offset().left;
var Y = event.pageY - $(this).offset().top;
$(".cordn").text("(" + X + "," + Y + ")");
});
});
You can see an example here: How to find mouse position relative to element
I +1' Mark van Wyk's answer as it got me in the right direction, but didn't quite solve it for me. I still had an offset on painting in elements contained within another element.
FOllowing solved it for me:
x = e.pageX - this.offsetLeft - $(elem).offset().left;
y = e.pageY - this.offsetTop - $(elem).offset().top;
In other words - i simply stacked all the offsets from all elements nested
Based on @Spider's solution, my non JQuery version is this:
// Get the container element's bounding box
var sides = document.getElementById("container").getBoundingClientRect();
// Apply the mouse event listener
document.getElementById("canvas").onmousemove = (e) => {
// Here 'self' is simply the current window's context
var x = (e.clientX - sides.left) + self.pageXOffset;
var y = (e.clientY - sides.top) + self.pageYOffset;
}
This works both with scrolling and zooming (in which case sometimes it returns floats).
you can get it by
var element = document.getElementById(canvasId);
element.onmousemove = function(e) {
var xCoor = e.clientX;
var yCoor = e.clientY;
}
I implemented an other solution that I think is very simple so I thought I'd share with you guys.
So, the problem for me was that the dragged div would jump to 0,0 for the mouse cursor. So I needed to capture the mouses position on the div to adjust the divs new position.
I read the divs PageX and PageY and set the top and left of the according to that and then to get the values to adjust the coordinates to keep the cursor in the initial position in the div I use a onDragStart listener and store the e.nativeEvent.layerX and e.nativeEvent.layerY that only in the initial trigger gives you the mouses position within the draggable div.
Example code :
onDrag={(e) => {
let newCoords;
newCoords = { x: e.pageX - this.state.correctionX, y: e.pageY - this.state.correctionY };
this.props.onDrag(newCoords, e, item.id);
}}
onDragStart={
(e) => {
this.setState({
correctionX: e.nativeEvent.layerX,
correctionY: e.nativeEvent.layerY,
});
}
I hope this will help someone that went through the same problems I went through :)
I tried all these solutions and due to my special setup with a matrix transformed container (panzoom library) none worked. This returns the correct value, even if zoomed and paned:
mouseevent(e) {
const x = e.offsetX,
y = e.offsetY
}
But only if there are no child elements in the way. This can be circumvented by making them 'invisible' to the event, using CSS:
.child {
pointer-events: none;
}
The mouse coordinates inside a canvas can be obtained thanks to event.offsetX and event.offsetY. Here's a little snippet to prove my point:
c=document.getElementById("c");_x000D_
ctx=c.getContext("2d");_x000D_
ctx.fillStyle="black";_x000D_
ctx.fillRect(0,0,100,100);_x000D_
c.addEventListener("mousemove",function(mouseEvt){_x000D_
// the mouse's coordinates on the canvas are just below_x000D_
x=mouseEvt.offsetX;_x000D_
y=mouseEvt.offsetY;_x000D_
// the following lines draw a red square around the mouse to prove it_x000D_
ctx.fillStyle="black";_x000D_
ctx.fillRect(0,0,100,100);_x000D_
ctx.fillStyle="red";_x000D_
ctx.fillRect(x-5,y-5,10,10);_x000D_
});
_x000D_
_x000D_
body {_x000D_
background-color: blue;_x000D_
}_x000D_
_x000D_
canvas {_x000D_
position: absolute;_x000D_
top: 50px;_x000D_
left: 100px;_x000D_
}
_x000D_
<canvas id="c" width="100" height="100"></canvas>_x000D_
_x000D_
function myFunction(e) {
var x = e.clientX - e.currentTarget.offsetLeft ;
var y = e.clientY - e.currentTarget.offsetTop ;
}
this works ok!
I realise I'm a little late , but this works with PURE javascript, and it even gives you the coordinates of the pointer within the element if the element is bigger than the viewport and the user has scrolled.
var element_offset_x ; // The distance from the left side of the element to the left of the content area
....// some code here (function declaration or element lookup )
element_offset_x = element.getBoundingClientRect().left - document.getElementsByTagName("html")[0].getBoundingClientRect().left ;
....// code here
function mouseMoveEvent(event)
{
var pointer_location = (event.clientX + window.pageXOffset) - element_offset_x ;
}
How it works.
The first thing we do is get the location of the HTML element (the content area) relative to the current viewport. If the page has scrollbars and is scrolled, then the number returned by getBoundingClientRect().left
for the html tag will be negative. We then use this number to compute the distance between the element and the left of the content area. With element_offset_x = element.getBoundingClientRect().left......;
Knowing the distance of the element from the content area. event.clientX
gives us the distance of the pointer from the viewport. It is important to understand that the viewport and the content area are two different entities, the viewport can move if the page is scrolled. Hence, clientX will return the SAME number even if the page is scrolled.
To compensate for this , we need to add the x position of the pointer (relative to the viewport) , to the x position of the viewport (relative to the content area ). The X position of the viewport is found with window.pageXOffset.
A good write up of the difficulty of this problem can be found here: http://www.quirksmode.org/js/events_properties.html#position
Using the technique that is described there you can find the mouses position in the document. Then you just check to see if it is inside the bounding box of your element, which you can find by calling element.getBoundingClientRect()
which will return an object with the following properties: { bottom, height, left, right, top, width }
. From there it is trivial to figure out if the even happened inside your element or not.
The following calculates the mouse position relation to the canvas element:
var example = document.getElementById('example');
example.onmousemove = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
}
In this example, this
refers to the example
element, and e
is the onmousemove
event.
canvas.onmousedown = function(e) {
pos_left = e.pageX - e.currentTarget.offsetLeft;
pos_top = e.pageY - e.currentTarget.offsetTop;
console.log(pos_left, pos_top)
}
HTMLElement.offsetLeft
The HTMLElement.offsetLeft
read-only property returns the number of pixels that the upper left corner of the current element is offset to the left within the HTMLElement.offsetParent
node.
For block-level elements, offsetTop
, offsetLeft
, offsetWidth
, and offsetHeight
describe the border box of an element relative to the offsetParent
.
However, for inline-level elements (such as span
) that can wrap from one line to the next, offsetTop
and offsetLeft
describe the positions of the first border box (use Element.getClientRects()
to get its width and height), while offsetWidth
and offsetHeight
describe the dimensions of the bounding border box (use Element.getBoundingClientRect()
to get its position). Therefore, a box with the left, top, width and height of offsetLeft
, offsetTop
, offsetWidth
and offsetHeight
will not be a bounding box for a span with wrapped text.
HTMLElement.offsetTop
The HTMLElement.offsetTop
read-only property returns the distance of the current element relative to the top of the offsetParent
node.
MouseEvent.pageX
The pageX
read-only property returns the X (horizontal) coordinate in pixels of the event relative to the whole document. This property takes into account any horizontal scrolling of the page.
MouseEvent.pageY
The MouseEvent.pageY
read-only property returns the Y (vertical) coordinate in pixels of the event relative to the whole document. This property takes into account any vertical scrolling of the page.
For further explanation, please see the Mozilla Developer Network:
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetLeft https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
I came across this question, but in order to make it work for my case (using dragover on a DOM-element (not being canvas in my case)), I found that you only have have to use offsetX
and offsetY
on the dragover-mouse event.
onDragOver(event){
var x = event.offsetX;
var y = event.offsetY;
}
None of the above answers are satisfactory IMO, so here's what I use:
// Cross-browser AddEventListener
function ael(e, n, h){
if( e.addEventListener ){
e.addEventListener(n, h, true);
}else{
e.attachEvent('on'+n, h);
}
}
var touch = 'ontouchstart' in document.documentElement; // true if touch device
var mx, my; // always has current mouse position IN WINDOW
if(touch){
ael(document, 'touchmove', function(e){var ori=e;mx=ori.changedTouches[0].pageX;my=ori.changedTouches[0].pageY} );
}else{
ael(document, 'mousemove', function(e){mx=e.clientX;my=e.clientY} );
}
// local mouse X,Y position in element
function showLocalPos(e){
document.title = (mx - e.getBoundingClientRect().left)
+ 'x'
+ Math.round(my - e.getBoundingClientRect().top);
}
And if you ever need to know the current Y scrolling position of page :
var yscroll = window.pageYOffset
|| (document.documentElement && document.documentElement.scrollTop)
|| document.body.scrollTop; // scroll Y position in page
You can use getBoudingClientRect()
of the relative
parent.
document.addEventListener("mousemove", (e) => {
let xCoord = e.clientX - e.target.getBoundingClientRect().left + e.offsetX
let yCoord = e.clientY - e.target.getBoundingClientRect().top + e.offsetY
console.log("xCoord", xCoord, "yCoord", yCoord)
})
There is no answer in pure javascript that returns relative coordinates when the reference element is nested inside others which can be with absolute positioning. Here is a solution to this scenario:
function getRelativeCoordinates (event, referenceElement) {
const position = {
x: event.pageX,
y: event.pageY
};
const offset = {
left: referenceElement.offsetLeft,
top: referenceElement.offsetTop
};
let reference = referenceElement.offsetParent;
while(reference){
offset.left += reference.offsetLeft;
offset.top += reference.offsetTop;
reference = reference.offsetParent;
}
return {
x: position.x - offset.left,
y: position.y - offset.top,
};
}
As I didn't find a jQuery-free answer that I could copy/paste, here's the solution I used:
document.getElementById('clickme').onclick = function clickEvent(e) {
// e = Mouse click event.
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left; //x position within the element.
var y = e.clientY - rect.top; //y position within the element.
console.log("Left? : " + x + " ; Top? : " + y + ".");
}
_x000D_
#clickme {
margin-top: 20px;
margin-left: 100px;
border: 1px solid black;
cursor: pointer;
}
_x000D_
<div id="clickme">Click Me -<br>
(this box has margin-left: 100px; margin-top: 20px;)</div>
_x000D_
I had to get the cursor position inside a very wide div with scrollbar. The objective was to drag elements to any position of the div.
To get the mouse position on a far away position deep in the scrolling.
$('.canvas').on('mousemove', function(e){
$(dragElement).parent().css('top', e.currentTarget.scrollTop + e.originalEvent.clientY );
$(dragElement).parent().css('left', e.currentTarget.scrollLeft + e.originalEvent.clientX )
});
Here is what I got.
$(".some-class").click(function(e) {
var posx = 0;
var posy = 0;
posx = e.pageX;
posy = e.pageY;
alert(posx);
alert(posy);
});
You have to know the structure of your page, because if your canvas is a child of a div which in turn is a child of another div... then the story gets more complicated. Here's my code for a canvas which is inside 2 levels of div s:
canvas.addEventListener("click", function(event) {
var x = event.pageX - (this.offsetLeft + this.parentElement.offsetLeft);
var y = event.pageY - (this.offsetTop + this.parentElement.offsetTop);
console.log("relative x=" + x, "relative y" + y);
});
Based on @Patrick Boos solution but fixing potential problem with intermediate scrollbars.
export function getRelativeCoordinates(event: MouseEvent, referenceElement: HTMLElement) {
const position = {
x: event.pageX,
y: event.pageY,
};
const offset = {
left: referenceElement.offsetLeft,
top: referenceElement.offsetTop,
};
let reference = referenceElement.offsetParent as HTMLElement;
while (reference) {
offset.left += reference.offsetLeft;
offset.top += reference.offsetTop;
reference = reference.offsetParent as HTMLElement;
}
const scrolls = {
left: 0,
top: 0,
};
reference = event.target as HTMLElement;
while (reference) {
scrolls.left += reference.scrollLeft;
scrolls.top += reference.scrollTop;
reference = reference.parentElement as HTMLElement;
}
return {
x: position.x + scrolls.left - offset.left,
y: position.y + scrolls.top - offset.top,
};
}
Original answer said to put it in an iframe. The better solution is to use the events offsetX and offsetY on a canvas that has the padding set to 0px.
<html>
<body>
<script>
var main=document.createElement('canvas');
main.width="200";
main.height="300";
main.style="padding:0px;margin:30px;border:thick dashed red";
document.body.appendChild(main);
// adding event listener
main.addEventListener('mousemove',function(e){
var ctx=e.target.getContext('2d');
var c=Math.floor(Math.random()*0xFFFFFF);
c=c.toString(16); for(;c.length<6;) c='0'+c;
ctx.strokeStyle='#'+c;
ctx.beginPath();
ctx.arc(e.offsetX,e.offsetY,3,0,2*Math.PI);
ctx.stroke();
e.target.title=e.offsetX+' '+e.offsetY;
});
// it worked! move mouse over window
</script>
</body>
</html>
Source: Stackoverflow.com