获取鼠标相对于元素内容区域的位置

13

当鼠标移动到一个元素上时,我想获取光标相对于该元素内容区域左上角的鼠标坐标(这个区域不包括填充、边框和轮廓)。听起来很简单,对吧?到目前为止,我有一个非常受欢迎的函数:

function element_position(e) {
    var x = 0, y = 0;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

我想获取鼠标相对于元素element的位置:

p = element_position(element);
x = mouseEvent.pageX - p.x;
y = mouseEvent.pageY - p.y;

那不是完全正确的。因为offsetLeftoffsetTop是元素“外部”左上角和其偏移父级的“内部”左上角之间的差异,所以求和位置将跳过层次结构中的所有边框和填充。

这里有一个比较,应该(希望)能澄清我的意思。

  • 如果我获取元素与其偏移父级的“内部”左上角之间的位置距离之和(外部减去内部;我正在做的事情),我得到元素的内容区域的位置,减去了偏移层次结构中的所有边框和填充。
  • 如果我获取元素与其偏移父级的“外部”左上角之间的位置距离之和(外部减去外部),我得到元素的内容区域的位置,减去了所需元素的边框和填充(接近,但还不够准确)。
  • 如果我获取元素与其偏移父级的“内部”左上角之间的位置距离之和(内部减去内部),我得到元素的内容区域的位置。这就是我想要的。

我个人更喜欢不在画布本身上应用CSS,而是将画布包装在一个元素内,并使用CSS进行装饰。这样可以减少很多麻烦。 ;) - Caspar Kleijne
这是真的,我不会在我的画布元素上应用填充/边框。只是一个理论问题,真的。 - Delan Azabani
1
似乎使用offsetLeft/Top方法获取元素位置不仅会受到目标元素边框和内边距的影响,还会受到所有偏移父元素的边框和内边距的影响。请查看我重新编写的问题,并看看您是否能够对我的情况发表意见。 - Delan Azabani
4个回答

10

以下是一个实时示例,使用了一个element_position()函数,该函数可以感知填充和边框。 我在您原始示例的基础上添加了一些额外的填充和边距。

http://jsfiddle.net/Skz8g/4/

要使用它,请将光标移动到棕色区域上。 得到的白色区域是实际画布内容的区域。 棕色是填充,红色是边框等。 在这个示例和之后的示例中,canvas xcanvas y读数显示与画布内容相关的光标位置。

以下是element_position()的代码:

function getNumericStyleProperty(style, prop){
    return parseInt(style.getPropertyValue(prop),10) ;
}

function element_position(e) {
    var x = 0, y = 0;
    var inner = true ;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
        var style = getComputedStyle(e,null) ;
        var borderTop = getNumericStyleProperty(style,"border-top-width") ;
        var borderLeft = getNumericStyleProperty(style,"border-left-width") ;
        y += borderTop ;
        x += borderLeft ;
        if (inner){
          var paddingTop = getNumericStyleProperty(style,"padding-top") ;
          var paddingLeft = getNumericStyleProperty(style,"padding-left") ;
          y += paddingTop ;
          x += paddingLeft ;
        }
        inner = false ;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

代码应该能够在IE9、FF和Chrome上正常工作,尽管我注意到它在Opera上还不太正确。

我的最初想法是使用类似于e.offsetX/Y属性,因为它们更接近您想要的内容,并且不涉及循环嵌套元素。但是,它们在不同的浏览器上行为差异很大,因此需要进行一些跨浏览器的调整。示例代码如下:

http://jsfiddle.net/xUZAa/6/

它应该在所有现代浏览器上都能正常工作- Opera,FF,Chrome,IE9。我个人更喜欢它,但是我认为,尽管您最初的问题只是关于“获取与元素内容区域相关的鼠标位置”,但您实际上是在问如何使element_position()函数正常工作。


1
谢谢你的回答,也感谢其他人的回答。我特别接受你的回答,因为它没有使用外部库,并且选择了你的回答而不是Aleadam的,因为你费心创建了代码和演示(虽然Aleadam,你完全正确,所以感谢你的回答!)。这个任务似乎很简单,但必须手动实现;我真的认为,在规范中应该至少有原生属性,如offsetLeftoffsetTop,但用于内容区域位置。 - Delan Azabani
1
@Pi,我猜可能取决于光标图标的“起点”。有可能起点不在箭头尖端,而是在你意料之外的地方。你可以尝试使用十字线光标,看看会得到什么结果。 - brainjam
1
@Pi,这可能会有所帮助:http://www.w3schools.com/cssref/tryit.asp?filename=trycss_cursor - brainjam
太棒了!我已经将它编辑到答案片段中。 - P i
当我尝试这个时,它似乎没有考虑页面的滚动条。我使用的是Firefox 45.6.0版本。 - Mutant Bob
显示剩余2条评论

4

使用jQuery:

function posRelativeToElement(elem, ev){
    var $elem = $(elem),
         ePos = $elem.offset(),
     mousePos = {x: ev.pageX, y: ev.pageY};

    mousePos.x -= ePos.left + parseInt($elem.css('paddingLeft')) + parseInt($elem.css('borderLeftWidth'));
    mousePos.y -= ePos.top + parseInt($elem.css('paddingTop')) + parseInt($elem.css('borderTopWidth'));

    return mousePos;
};

实时示例:http://jsfiddle.net/vGKM3/

其根本原理很简单:计算元素相对于文档的位置。然后去掉顶部和左侧的填充和边框(通过基本定位计算包括了margin)。内部jQuery代码是基于getComputedStyleelement.currentStyle来完成这个过程的。不幸的是,我不认为还有其他方法……

jQuery的.offset()函数的核心功能是获取一个元素相对于文档的位置:

if ( "getBoundingClientRect" in document.documentElement ) {
    ...
    try {
        box = elem.getBoundingClientRect();
    } catch(e) {}

    var body = doc.body,
        win = getWindow(doc),
        clientTop  = docElem.clientTop  || body.clientTop  || 0,
        clientLeft = docElem.clientLeft || body.clientLeft || 0,
        scrollTop  = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop ),
        scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
        top  = box.top  + scrollTop  - clientTop,
        left = box.left + scrollLeft - clientLeft;

    return { top: top, left: left };
}else{
    // calculate recursively based on .parentNode and computed styles
}

理论上,可以使用上面的定位代码来实现另一种方式:
  • 确保您的元素已设置 position:relative (或 absolute)
  • 附加一个具有 position: absolute; top:0px; left:0px; 的新元素
  • 获取相对于文档的新元素位置。它将与父元素的内容位置相同
  • 删除新元素

2
在您的element_position(e)函数中,通过使用parentNode迭代整个层次结构,并使用getComputedStyle(e, null).getPropertyValue(each_css)获取填充、偏移和边框的值,将它们加到xy的值中,然后返回总和。这里有一篇帖子提出了跨浏览器读取样式的建议:

http://bytes.com/topic/javascript/answers/796275-get-div-padding


1

我不确定这是否是最好的方式,或者最节约资源的方式...

但我建议获取画布标签的X/Y坐标、边框宽度和填充,并将它们一起作为偏移量使用。

编辑:

使用offsetLeft和offsetTop

参考:如何在HTML5中使用画布和绘制元素

var x;
var y;
if (e.pageX || e.pageY) { 
  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;

更新了答案,附上一段代码,我在https://dev59.com/ZXVD5IYBdhLWcg3wNIrc找到的,作者是N4ppeL -- 使用offsetLeft和offsetTop。 - David Houde
请查看我完全重写的问题,并让我知道您是否可以对此发表意见。;) - Delan Azabani
抱歉兄弟,我想我在这里有些超出自己的能力范围了。我不想手工获取所有的样式,除非你使用一个框架,否则我认为没有一个简单的跨浏览器解决方案。http://www.quirksmode.org/dom/getstyles.html - David Houde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接