查找鼠标相对于元素的位置


244

我想使用画布制作一个绘画应用程序。因此,我需要找到鼠标在画布上的位置。


2
完整解决方案:acko.net/blog/…–
edA-qa mort-ora-y

3
我喜欢这么短,但是可以解释这篇文章的所有内容。
dwjohnston '16

这确实很老,但是我刚刚开始了一个GitHub项目,该项目可以完全满足您的需求:brainlessdeveloper.com/mouse-move-interaction-spot-js
Fausto NA

Answers:


177

对于使用JQuery的人:

有时,当您具有嵌套元素时,其中一个元素已附加了事件,这可能会使您难以理解您的浏览器将其视为父级。在这里,您可以指定哪个父级。

您采用鼠标位置,然后从父元素的偏移位置中减去它。

var x = evt.pageX - $('#element').offset().left;
var y = evt.pageY - $('#element').offset().top;

如果要在滚动窗格内的页面上获取鼠标位置:

var x = (evt.pageX - $('#element').offset().left) + self.frame.scrollLeft();
var y = (evt.pageY - $('#element').offset().top) + self.frame.scrollTop();

或相对于页面的位置:

var x = (evt.pageX - $('#element').offset().left) + $(window).scrollLeft();
var y = (evt.pageY - $('#element').offset().top) + $(window).scrollTop();

请注意以下性能优化:

var offset = $('#element').offset();
// Then refer to 
var x = evt.pageX - offset.left;

这样,JQuery不必查找#element每一行。

更新资料

下面的@anytimecoder为支持getBoundingClientRect()的浏览器提供了一个更高的纯JavaScript版本。


7
它对您有用吗@ben,对于“仍然正确的答案”或对仍然不起作用的内容的评论,我将不胜感激。也许我可以提供进一步的帮助。
sparkyspider

8
应该避免在事件处理程序中使用$('selector'),除非您需要非常慢的代码。预选择您的元素,然后在处理程序中使用预选择的引用。与$(window)相同,只执行一次并将其缓存。
法国奥斯汀,

2
这不是必须提及的内容:如果您要复制粘贴上面的代码,请记住更正var y = (evt.pageY - $('#element').offset().left) + $(window).scrollTop();var y = (evt.pageY - $('#element').offset().top) + $(window).scrollTop();
Raj Nathani 2014年

77
既然问题与jQuery无关,那怎么可能是答案?
FlorianB

2
@Black,但是有些人用javascript而不是jquery询问问题,因为jquery在他们的环境中可能不兼容。
dbrree

307

由于找不到可复制/粘贴的无jQuery答案,因此使用了以下解决方案:

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.
}

JSFiddle的完整示例


2
您需要使用此方法对边界矩形进行四舍五入以确保整数值。
杰克·科布

2
新的编辑应解决滚动问题(使用clientX / Y)
Erik Koopmans

6
这个答案应该投票通过。谢谢anytimecoder和@ErikKoopmans。
伊万(Ivan)

3
PSA:@mpen的评论已过时。即使滚动,这种方法也能奏效。参见其提琴的
Xharlie

4
谢谢!请注意:您需要e.currentTarget,而不是e.target。否则,儿童元素会影响它
Maxim Georgievskiy

60

以下计算鼠标与画布元素的位置关系:

var example = document.getElementById('example'); 
example.onmousemove = function(e) { 
    var x = e.pageX - this.offsetLeft; 
    var y = e.pageY - this.offsetTop; 
}

在此示例中,this引用example元素,并且eonmousemove事件。


83
也许是我,但是使用关键字“ this”的两行代码缺少上下文?
Deratrius 2012年

9
绝对缺乏背景。“ e”是事件,“这”是事件相关的元素 var example = document.getElementById('example'); example.onmousemove = function(e) { var X = e.pageX - this.offsetLeft; var Y = e.pageY - this.offsetTop; }
Nick Bisby

22
如果某些后代元素使用相对或绝对定位,则此方法不起作用。
edA-qa mort-ora-y 2013年

12
您可以切换thise.currentTarget,这将使您相对于向其添加事件侦听器的元素的位置。
LinusUnnebäck,2015年

2
这似乎使位置与元素的可见部分相关,而不是整个元素。如果在视口中看不到元素的一部分并且有滚动条等,是否有办法解决?
frabjous

37

当参考元素嵌套在可以绝对定位的其他元素内时,纯JavaScript没有答案会返回相对坐标。这是此方案的解决方案:

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,
  }; 

}

3
我认为这并未考虑html元素偏移量
apieceofbart

3
只需稍作改动,即可与滚动容器配合使用:替换while(reference !== undefined)while(reference)。至少在Chrome中,offsetParent最终不是undefined,而是null。仅检查是否reference为真将确保它与undefined和都适用null
blex

如果可以的话,我会再给+1,因为让我知道有一个offsetParent属性,这在其他情况下也非常有用!
FonzTech

通常,检查未定义通常是一个坏主意
Juan Mendes

我认为您需要减去scrollLeftscrollTop补偿滚动后代。
罗伯特

20

可以在以下位置找到有关此问题的难度的完整文章:http : //www.quirksmode.org/js/events_properties.html#position

使用此处描述的技术,您可以在文档中找到鼠标的位置。然后,您只需检查它是否在元素的边界框中即可,您可以通过调用边界框来找到element.getBoundingClientRect()它,该方法将返回具有以下属性的对象:{ bottom, height, left, right, top, width }。从那里很容易判断出偶数是否发生在您的元素内部。


1
遗憾的是,如果发生任何形式的轮换,此操作将失败
Eric

17

我尝试了所有这些解决方案,并且由于我对矩阵转换的容器(panzoom库)进行了特殊设置而无法使用。即使缩放和平移,这也会返回正确的值:

mouseevent(e) {
 const x = e.offsetX,
       y = e.offsetY
}

但前提是没有子元素在路上。可以通过使用CSS使事件“不可见”来规避此问题:

.child {
   pointer-events: none;
}

3
这绝对是在2019年做到这一点的方式。其他答案都充满了旧的沉重行李。
Colin D

1
@ColinD谢谢!我忘了补充说,这也可以在IE11中使用。
Fabian von Ellerts,

这对我有用。但是,我怀疑1)很少投票2)评分较高的答案更为复杂,3)我看不出有任何缺点。有谁知道使用这种方法有什么弊端?
Mark E

1
@MarkE对此没有任何怀疑。这是StackOverflow的许多故障之一。您在顶部看到的那些答案在此答案之前已经发布了很多年,因此它们获得了巨大的否决权。一旦答案获得了这种动力,就很难克服它了。
杰克·吉芬

9

我遇到了这个问题,但是为了使其适用于我的情况(在DOM元素上使用拖移(在我的情况下不是画布)),我发现您只需要使用,offsetXoffsetY在dragover-mouse事件上。

onDragOver(event){
 var x = event.offsetX;
 var y = event.offsetY;
}

2
这对我来说很棒。为什么没有人对此表示支持?我想知道是否有任何陷阱。如果发现任何问题,我会报告!
鲈鱼

7
event.offsetX相对于鼠标指针当前位于的元素,而不是附加了拖动侦听器的元素。如果您没有嵌套的元素结构,那么这很好,但是如果您这样做,则这将行不通。
opsb

7

我+1了范·范·维克(Mark van Wyk)的答案,因为它可以使我朝正确的方向发展,但并不能完全解决我的问题。在包含在另一个元素中的元素上绘画时,我仍然有偏差。

以下为我解决了这个问题:

        x = e.pageX - this.offsetLeft - $(elem).offset().left;
        y = e.pageY - this.offsetTop - $(elem).offset().top;

换句话说-我只是堆叠所有嵌套元素的所有偏移量


为您+1!这就是我一直在寻找的答案。我有两张画布彼此叠放,由于其他div,坐标都错了。我知道这是问题所在,但不知道补偿的逻辑/等式。您也解决了我的问题!= D谢谢!
Partack 2013年

5

上面的答案都不是令人满意的IMO,因此,我使用的是:

// 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);
}

并且如果您需要知道页面的当前Y滚动位置:

var yscroll = window.pageYOffset
        || (document.documentElement && document.documentElement.scrollTop)
        || document.body.scrollTop; // scroll Y position in page

4

对于那些为带触摸屏的移动设备和/或笔记本电脑/显示器开发常规网站或PWA(渐进式Web应用程序)的人来说,您之所以落在这里是因为您可能习惯于鼠标事件,并且不熟悉Touch有时带来的痛苦体验事件...是的!

只有3条规则:

  1. mousemovetouchmove事件期间,尽可能少做。
  2. mousedowntouchstart事件期间尽可能多地做。
  3. 取消传播并阻止默认的触摸事件,以防止在混合设备上触发鼠标事件。

不用说,touch事件使事情变得更加复杂,因为事件不只一个,而且它们比鼠标事件更灵活(复杂)。我这里只涉及一个方面。是的,我很懒,但这是最常见的触摸方式,所以就在那里。

var posTop;
var posLeft;
function handleMouseDown(evt) {
  var e = evt || window.event; // Because Firefox, etc.
  posTop = e.target.offsetTop;
  posLeft = e.target.offsetLeft;
  e.target.style.background = "red";
  // The statement above would be better handled by CSS
  // but it's just an example of a generic visible indicator.
}
function handleMouseMove(evt) {
  var e = evt || window.event;
  var x = e.offsetX; // Wonderfully
  var y = e.offsetY; // Simple!
  e.target.innerHTML = "Mouse: " + x + ", " + y;
  if (posTop)
    e.target.innerHTML += "<br>" + (x + posLeft) + ", " + (y + posTop);
}
function handleMouseOut(evt) {
  var e = evt || window.event;
  e.target.innerHTML = "";
}
function handleMouseUp(evt) {
  var e = evt || window.event;
  e.target.style.background = "yellow";
}
function handleTouchStart(evt) {
  var e = evt || window.event;
  var rect = e.target.getBoundingClientRect();
  posTop = rect.top;
  posLeft = rect.left;
  e.target.style.background = "green";
  e.preventDefault(); // Unnecessary if using Vue.js
  e.stopPropagation(); // Same deal here
}
function handleTouchMove(evt) {
  var e = evt || window.event;
  var pageX = e.touches[0].clientX; // Touches are page-relative
  var pageY = e.touches[0].clientY; // not target-relative
  var x = pageX - posLeft;
  var y = pageY - posTop;
  e.target.innerHTML = "Touch: " + x + ", " + y;
  e.target.innerHTML += "<br>" + pageX + ", " + pageY;
  e.preventDefault();
  e.stopPropagation();
}
function handleTouchEnd(evt) {
  var e = evt || window.event;
  e.target.style.background = "yellow";
  // Yes, I'm being lazy and doing the same as mouseout here
  // but obviously you could do something different if needed.
  e.preventDefault();
  e.stopPropagation();
}
div {
  background: yellow;
  height: 100px;
  left: 50px;
  position: absolute;
  top: 80px;
  user-select: none; /* Disable text selection */
  -ms-user-select: none;
  width: 100px;
}
<div 
  onmousedown="handleMouseDown()" 
  onmousemove="handleMouseMove()"
  onmouseout="handleMouseOut()"
  onmouseup="handleMouseUp()" 
  ontouchstart="handleTouchStart()" 
  ontouchmove="handleTouchMove()" 
  ontouchend="handleTouchEnd()">
</div>
Move over box for coordinates relative to top left of box.<br>
Hold mouse down or touch to change color.<br>
Drag to turn on coordinates relative to top left of page.

更喜欢使用Vue.js吗?我做!然后,您的HTML将如下所示:

<div @mousedown="handleMouseDown"
     @mousemove="handleMouseMove"
     @mouseup="handleMouseUp"
     @touchstart.stop.prevent="handleTouchStart"
     @touchmove.stop.prevent="handleTouchMove"
     @touchend.stop.prevent="handleTouchEnd">

3

你可以得到它

var element = document.getElementById(canvasId);
element.onmousemove = function(e) {
    var xCoor = e.clientX;
    var yCoor = e.clientY;
}

它将在IE中工作,还是IE仍使用window.event而不是将事件传递给侦听器的第一个参数?编辑-我想IE缺乏canvas元素反正直到9版本,但仍可能应该支持IE的原因之类的东西excanvas ....
Dagg Nabbit

21
这将提供相对于浏览器窗口的坐标,仍然需要使用element.getBoundingClientRect()找出它们是否在元素中
David W. Keith 2010年

感谢您让我知道getBoundingClientRect !!!! 我从来没有意识到有这样的事情!
Gershom 2014年

@ DavidW.Keith评论确实应该被视为正确答案!
Ulrik。S

3

摘自本教程,并通过顶部注释进行了更正:

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 )
    };
}

在画布上使用,如下所示:

var canvas = document.getElementById( 'myCanvas' );
canvas.addEventListener( 'mousemove', function( evt ) {
    var mousePos = getMousePos( canvas, evt );
} );

3

我意识到我来晚了一点,但是这适用于PURE javascript,如果该元素大于视口并且用户已经滚动,它甚至可以为您提供该元素内指针的坐标。

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 ; 
}

这个怎么运作。

我们要做的第一件事是获取HTML元素(内容区域)相对于当前视口的位置。如果页面具有滚动条并被滚动,则返回的数字getBoundingClientRect().left html标记将为负。然后,我们使用此数字来计算元素与内容区域左侧之间的距离。用element_offset_x = element.getBoundingClientRect().left......;

知道元素与内容区域的距离。event.clientX给我们指针从视口的距离。重要的是要了解视口和内容区域是两个不同的实体,如果滚动页面,视口可以移动。因此,即使滚动页面,clientX也将返回相同的编号。

为了弥补这一点,我们需要将指针的x位置(相对于视口)添加到视口的x位置(相对于内容区域)。找到视口的X位置 window.pageXOffset.


1
感谢您是唯一考虑滚动的答案。考虑更改您的答案以同时添加y轴。
frabjous

3

基于@Spider的解决方案,我的非JQuery版本是这样的:

// 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;
}

这对滚动和缩放都有效(在这种情况下有时返回浮点数)。


我会投票赞成,但那不应该是页面偏移量的负号吗?或者是第二部分的括号?
克里斯·苏纳米

1

借助event.offsetX和event.offsetY可以获取画布内的鼠标坐标。以下是一些片段来证明我的观点:

c=document.getElementById("c");
ctx=c.getContext("2d");
ctx.fillStyle="black";
ctx.fillRect(0,0,100,100);
c.addEventListener("mousemove",function(mouseEvt){
  // the mouse's coordinates on the canvas are just below
  x=mouseEvt.offsetX;
  y=mouseEvt.offsetY;
  // the following lines draw a red square around the mouse to prove it
  ctx.fillStyle="black";
  ctx.fillRect(0,0,100,100);
  ctx.fillStyle="red";
  ctx.fillRect(x-5,y-5,10,10);
});
  
body {
  background-color: blue;
}

canvas {
  position: absolute;
  top: 50px;
  left: 100px;
}
<canvas id="c" width="100" height="100"></canvas>
    


如果另一个div覆盖画布,则无法正常工作,就像@opsb所说的那样
David Peicho

1
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

HTMLElement.offsetLeft只读属性返回,所述当前元素的左上角中的偏移到左侧的像素的数目HTMLElement.offsetParent的节点。

对于块级元素,offsetTopoffsetLeftoffsetWidth,和offsetHeight描述元件相对于所述一个的边界框offsetParent

然而,对于行内的元素(如span),其可以包从一行到下一,offsetTop并且offsetLeft描述第一边界框中的位置(使用Element.getClientRects()以获得其宽度和高度),而offsetWidthoffsetHeight描述的边界的边界框的尺寸(用于Element.getBoundingClientRect()获取其位置)。因此,与左,顶,宽度和高度的箱offsetLeftoffsetTopoffsetWidthoffsetHeight将不与换行文本的跨度的边界框。

HTMLElement.offsetTop

HTMLElement.offsetTop只读属性返回电流元件相对于所述的顶部的距离offsetParent的节点。

MouseEvent.pageX

pageX只读属性返回事件相对于整个文档的像素的坐标的X(水平)。此属性考虑页面的任何水平滚动。

MouseEvent.pageY

MouseEvent.pageY只读属性返回Y(垂直)在事件相对于整个文档的像素的坐标。此属性考虑页面的任何垂直滚动。

有关更多说明,请参见Mozilla开发人员网络:

https://developer.mozilla.org/zh-CN/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/offset 左https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop


尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
Patrick Hund

我真的希望以后的读者可以使用JS API来为他们进行计算。;-)这可能是最简单,完整,独立的答案。注释不言自明的代码只是分散注意力。
lama12345

我第二次@PatrickHund请求解释。我不是一个CSS / JS专家,更多地了解之间,例如关系,pageoffset就对我很有帮助。感谢您考虑此要求!
cxw

@cxw好吧,您现在是第二次downvote(有1次upvote)...现在我加了一个长的解释,就像四行代码一样,它是10x。欢迎您,阅读愉快。我考虑了您的要求!:p
lama12345

1
@cxw我通过添加一个额外的绝对定位元素并迫使canvas元素本身脱离屏幕而搞乱了,所以我不得不滚动到它,就像左/顶部2000px脱离屏幕。无法中断此代码。
lama12345

1

我实现了另一个我认为非常简单的解决方案,因此我想与大家分享。

因此,对我来说,问题是鼠标光标的div跳到0,0。因此,我需要捕获div上的鼠标位置以调整divs的新位置。

我阅读了divs PageX和PageY并根据其设置了顶部和左侧,然后获取值以调整坐标以将光标保持在div中的初始位置,我使用了onDragStart侦听器并存储了e.nativeEvent .layerXe.nativeEvent.layerY仅在初始触发器中为您提供鼠标在可拖动div中的位置。

示例代码:

 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,
            });
          }

我希望这对遇到过同样问题的人有所帮助:)


1
function myFunction(e) {
    var x =  e.clientX - e.currentTarget.offsetLeft ; 
    var y = e.clientY - e.currentTarget.offsetTop ;
}

这样行得通!


0

您必须知道页面的结构,因为如果您的画布是div的子级,而子级又是另一个div的子级,那么故事就变得更加复杂。这是我在div的2个级别内的画布的代码:

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);

});


0

原始答案说是将其放入iframe中。更好的解决方案是在填充设置为0px的画布上使用offsetX和offsetY事件。

<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>

0

这就是我得到的。

    $(".some-class").click(function(e) {

    var posx = 0;
    var posy = 0;

    posx = e.pageX;
    posy = e.pageY;

    alert(posx);
    alert(posy);
});

0

您可以使用getBoudingClientRect()的的relative父。

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)
})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.