如何获得具有CSS3转换元素的MouseEvent坐标?

我想检测一个MouseEvent发生在相对于被点击元素的坐标中 。 为什么? 因为我想在点击的位置添加一个绝对定位的子元素。

我知道如何检测不存在CSS3转换(请参阅下面的说明)。 但是,当我添加一个CSS3变换,然后我的algorithm中断,我不知道如何解决它。

我没有使用任何JavaScript库,我想了解如何在普通的JavaScript中工作。 所以,请不要用“只用jQuery”来回答。

顺便说一句,我想要一个适用于所有MouseEvent的解决scheme,而不仅仅是“点击”。 这并不重要,因为我相信所有的鼠标事件都具有相同的属性,因此相同的解决scheme应该适用于所有的鼠标事件。


背景信息

根据DOM Level 2规范 , MouseEvent几乎没有与获取事件坐标相关的属性:

  • screenXscreenY返回屏幕坐标(原点是用户显示器的左上angular)
  • clientXclientY返回相对于文档视口的坐标。

因此,为了findMouseEvent相对于单击的元素内容的位置,我必须做这个math:

 ev.clientX - this.getBoundingClientRect().left - this.clientLeft + this.scrollLeft 
  • ev.clientX是相对于文档视口的坐标
  • this.getBoundingClientRect().left是元素相对于文档视口的位置
  • this.clientLeft是元素边界和内部坐标之间的边界(和滚动条)的数量
  • this.scrollLeft是元素内滚动的数量

getBoundingClientRect()clientLeftscrollLeft在CSSOM视图模块中指定。

没有CSS变换的实验(它的工作原理)

混乱? 尝试下面的一段JavaScript和HTML 。 点击后,一个红点应该出现在点击发生的地方。 这个版本“非常简单”,并按预期工作。

 function click_handler(ev) { var rect = this.getBoundingClientRect(); var left = ev.clientX - rect.left - this.clientLeft + this.scrollLeft; var top = ev.clientY - rect.top - this.clientTop + this.scrollTop; var dot = document.createElement('div'); dot.setAttribute('style', 'position:absolute; width: 2px; height: 2px; top: '+top+'px; left: '+left+'px; background: red;'); this.appendChild(dot); } document.getElementById("experiment").addEventListener('click', click_handler, false); <div id="experiment" style="border: 5px inset #AAA; background: #CCC; height: 400px; position: relative; overflow: auto;"> <div style="width: 900px; height: 2px;"></div> <div style="height: 900px; width: 2px;"></div> </div> 

添加CSS变换的实验(失败)

现在, 尝试添加一个CSS transform

 #experiment { transform: scale(0.5); -moz-transform: scale(0.5); -o-transform: scale(0.5); -webkit-transform: scale(0.5); /* Note that this is a very simple transformation. */ /* Remember to also think about more complex ones, as described below. */ } 

该algorithm不知道转换,从而计算错误的位置。 更重要的是,Firefox 3.6和Chrome 12的结果是不同的。Opera 11.50的行为就像Chrome一样。

在这个例子中,唯一的变换是缩放,所以我可以乘以缩放因子来计算正确的坐标。 但是,如果我们考虑任意转换(缩放,旋转,倾斜,平移,matrix)甚至嵌套转换(另一个转换元素内的转换元素),那么我们确实需要更好的方法来计算坐标。

你正在经历的行为是正确的,你的algorithm不会中断。 首先CSS3变换被devise为不干扰盒子模型。

尝试和解释…

在元素上应用CSS3变换时。 元素呈现一种相对定位。 因为周围的元素不受变换元素的影响。

例如想象三个div在水平行。 如果应用缩放变换来减小中心div的大小。 周围的div不会向内移动,占据曾经占据转换元素的空间。

例如: http : //jsfiddle.net/AshMokhberi/bWwkC/

所以在盒子模型中,元素实际上并没有改变大小。 只有它的大小发生变化。

你还必须记住,你正在应用一个缩放变换,所以你的元素“真实”的大小实际上是相同的原始大小。 你只是在改变它的大小。

解释..

假设你创build一个宽度为1000px的div,并将其缩小到1/2的大小。 div的内部大小仍然是1000px,而不是500px。

所以你的点的位置是正确的相对于div的“真实”的大小。

我修改你的例子来说明。

说明

  1. 点击div,让你的鼠标在相同的位置。
  2. 在错误的位置find点。
  3. 按Q,div将成为正确的大小。
  4. 移动鼠标,find你点击的位置正确的点。

http://jsfiddle.net/AshMokhberi/EwQLX/

因此,为了使鼠标点击坐标与div上的可见位置相匹配,您需要了解鼠标正在根据窗口返回坐标,并且您的div偏移也是基于其“真实”大小。

由于您的对象大小是相对于窗口唯一的解决办法是缩放与您的div相同的比例值的偏移坐标。

然而,根据你设置div的Transform-origin属性,这可能会变得棘手。 因为这将影响抵消。

看这里。

http://jsfiddle.net/AshMokhberi/KmDxj/

希望这可以帮助。

如果元素是容器并且是绝对的或者相对的,你可以把它放在它的元素里面,把它放在父元素的相对位置,width = 1px,height = 1px,并移动到容器的内部,每次移动后使用document.elementFromPoint(event。 clientX,event.clientY)=))))

您可以使用二进制search,使其更快。 看起来很糟糕,但是很有效

http://jsfiddle.net/3VT5N/3/ – 演示

另外,可以使用Webkit webkitConvertPointFromPageToNode方法:

 var div = document.createElement('div'), scale, point; div.style.cssText = 'position:absolute;left:-1000px;top:-1000px'; document.body.appendChild(div); scale = webkitConvertPointFromNodeToPage(div, new WebKitPoint(0, 0)); div.parentNode.removeChild(div); scale.x = -scale.x / 1000; scale.y = -scale.y / 1000; point = webkitConvertPointFromPageToNode(element, new WebKitPoint(event.pageX * scale.x, event.pageY * scale.y)); point.x = point.x / scale.x; point.y = point.y / scale.x; 

要获取MouseEvent相对于单击元素的坐标,请使用offsetX / layerX

你有没有尝试过使用ev.layerXev.offsetX

  var offsetX =(typeof ev.offsetX ==“number”)?  ev.offsetX:ev.layerX ||  0; 

也可以看看:

  • CSSOM视图模块:9个MouseEvent接口的扩展
  • IE 8从元素的填充边缘而不是内容边缘测量clientX : GTalbot MSIE 8 Browser Bugs:event.offsetX,event.offsetY作为元素内的鼠标坐标目标的填充框
  • MSDN:offsetX属性

以最快的速度。 接受的答案需要大约40-70毫秒在我的3d变换网站,这通常需要less于20( 小提琴 ):

 function getOffset(event,elt){ var st=new Date().getTime(); var iterations=0; //if we have webkit, then use webkitConvertPointFromPageToNode instead if(webkitConvertPointFromPageToNode){ var webkitPoint=webkitConvertPointFromPageToNode(elt,new WebKitPoint(event.clientX,event.clientY)); //if it is off-element, return null if(webkitPoint.x<0||webkitPoint.y<0) return null; return { x: webkitPoint.x, y: webkitPoint.y, time: new Date().getTime()-st } } //make full-size element on top of specified element var cover=document.createElement('div'); //add styling cover.style.cssText='height:100%;width:100%;opacity:0;position:absolute;z-index:5000;'; //and add it to the document elt.appendChild(cover); //make sure the event is in the element given if(document.elementFromPoint(event.clientX,event.clientY)!==cover){ //remove the cover cover.parentNode.removeChild(cover); //we've got nothing to show, so return null return null; } //array of all places for rects var rectPlaces=['topleft','topcenter','topright','centerleft','centercenter','centerright','bottomleft','bottomcenter','bottomright']; //function that adds 9 rects to element function addChildren(elt){ iterations++; //loop through all places for rects rectPlaces.forEach(function(curRect){ //create the element for this rect var curElt=document.createElement('div'); //add class and id curElt.setAttribute('class','offsetrect'); curElt.setAttribute('id',curRect+'offset'); //add it to element elt.appendChild(curElt); }); //get the element form point and its styling var eltFromPoint=document.elementFromPoint(event.clientX,event.clientY); var eltFromPointStyle=getComputedStyle(eltFromPoint); //Either return the element smaller than 1 pixel that the event was in, or recurse until we do find it, and return the result of the recursement return Math.max(parseFloat(eltFromPointStyle.getPropertyValue('height')),parseFloat(eltFromPointStyle.getPropertyValue('width')))<=1?eltFromPoint:addChildren(eltFromPoint); } //this is the innermost element var correctElt=addChildren(cover); //find the element's top and left value by going through all of its parents and adding up the values, as top and left are relative to the parent but we want relative to teh wall for(var curElt=correctElt,correctTop=0,correctLeft=0;curElt!==cover;curElt=curElt.parentNode){ //get the style for the current element var curEltStyle=getComputedStyle(curElt); //add the top and left for the current element to the total correctTop+=parseFloat(curEltStyle.getPropertyValue('top')); correctLeft+=parseFloat(curEltStyle.getPropertyValue('left')); } //remove all of the elements used for testing cover.parentNode.removeChild(cover); //the returned object var returnObj={ x: correctLeft, y: correctTop, time: new Date().getTime()-st, iterations: iterations } return returnObj; } 

并在同一页面中包含以下CSS:

 .offsetrect{ position: absolute; opacity: 0; height: 33.333%; width: 33.333%; } #topleftoffset{ top: 0; left: 0; } #topcenteroffset{ top: 0; left: 33.333%; } #toprightoffset{ top: 0; left: 66.666%; } #centerleftoffset{ top: 33.333%; left: 0; } #centercenteroffset{ top: 33.333%; left: 33.333%; } #centerrightoffset{ top: 33.333%; left: 66.666%; } #bottomleftoffset{ top: 66.666%; left: 0; } #bottomcenteroffset{ top: 66.666%; left: 33.333%; } #bottomrightoffset{ top: 66.666%; left: 66.666%; } 

它基本上将元素分成9个正方形,通过document.elementFromPoint确定点击的位置。 然后将其分成9个小方块,直到精确到像素内。 我知道我过分评论它。 接受的答案比这慢了好几倍。

编辑:它现在更快了,如果用户在Chrome或Safari,它将使用本地function,而不是9个部门thingy,可以一致地做到less于2个MILLISECONDS!

另一种方法是在该元素的angular落放置3个div,比find变换matrix…但也仅适用于定位的可装载元素 – 4esn0k

演示: http : //jsfiddle.net/dAwfF/3/

这似乎对我来说真的很好

 var elementNewXPosition = (event.offsetX != null) ? event.offsetX : event.originalEvent.layerX; var elementNewYPosition = (event.offsetY != null) ? event.offsetY : event.originalEvent.layerY; 

无论是相对还是绝对:)简单的解决scheme

 var p = $( '.divName' ); var position = p.position(); var left = (position.left / 0.5); var top = (position.top / 0.5); 

我正在从一个polyfill转换DOM坐标。 GeometryUtils API还不可用(@请参阅https://drafts.c​​sswg.org/cssom-view/ )。 我在2014年创build了一个“简单”的代码来转换坐标,如localToGlobal,globalToLocal和localToLocal。 它还没有完成,但它的工作:)我想我会在未来几个月内完成(05.07.2017),所以如果你仍然需要一个API来完成坐标转换,请尝试: https : //github.com/ jsidea / jsidea jsidea核心图书馆 。 它尚不稳定 (前阿尔法)。 你可以这样使用它:

创build您的转换实例:

 var transformer = jsidea.geom.Transform.create(yourElement); 

您想要转换为的框模型(默认:“border”,稍后将由ENUM取代):

 var toBoxModel = "border"; 

input坐标来自的框模型(默认值:“border”):

 var fromBoxModel = "border"; 

将您的全局坐标(这里{x:50,y:100,z:0})转换为本地空间。 得到的点有4个分量:x,y,z和w。

 var local = transformer.globalToLocal(50, 100, 0, toBoxModel, fromBoxModel); 

我已经实现了一些其他的function,如localToGlobal和localToLocal。 如果您想尝试,只需下载发布版本并使用jsidea.min.js。

在这里下载第一个版本: 下载TypeScript代码

随意更改代码,我从来没有把它放在任何许可证:)

我有这个问题,并开始尝试计算matrix。 我开始了一个图书馆: https : //github.com/ombr/referentiel

 $('.referentiel').each -> ref = new Referentiel(this) $(this).on 'click', (e)-> input = [e.pageX, e.pageY] p = ref.global_to_local(input) $pointer = $('.pointer', this) $pointer.css('left', p[0]) $pointer.css('top', p[1]) 

你怎么看 ?

编辑:我的答案是未经testing,WIP,我会更新,当我得到它的工作。

我正在实现一个geomtetry接口的polyfill 。 DOMPoint.matrixTransform我将做的DOMPoint.matrixTransform方法,这意味着我们应该能够写下如下内容,以便将点击坐标映射到转换的(可能嵌套的)DOM元素上:

 // target is the element nested somewhere inside the scene. function multiply(target) { let result = new DOMMatrix; while (target && /* insert your criteria for knowing when you arrive at the root node of the 3D scene*/) { const m = new DOMMatrix(target.style.transform) result.preMultiplySelf(m) // see w3c DOMMatrix (geometry-interfaces) target = target.parentNode } return result } // inside click handler // traverse from nested node to root node and multiply on the way const matrix = multiply(node) const relativePoint = DOMPoint(clickX, clickY, 0, 800).matrixTransform(matrix) 

relativePoint将是相对于您单击的元素表面的点。

一个W3C DOMMatrix可以用一个CSS转换string参数来构造,这使得在JavaScript中使用起来非常简单。

不幸的是,这还没有工作(只有Firefox有一个几何接口的实现,我的polyfill还没有接受一个CSS转换string)。 请参阅: https : //github.com/trusktr/geometry-interfaces/blob/7872f1f78a44e6384529e22505e6ca0ba9b30a4d/src/DOMMatrix.js#L15-L18

我会更新一次,我实现了这一点,并有一个工作的例子。 拉请求欢迎!

编辑:价值800是场景的angular度来看,我不知道这是什么,当我们打算做这样的东西的DOMPoint构造函数的第四个值。 另外,我不确定是否应该使用preMultiplySelfpostMultiplySelf 。 我会发现一旦我得到它至less工作(价值可能不正确的起初),并会更新我的答案。