Translating Viewport Coordinates Into Element-Local Coordinates Using Element.getBoundingClientRect()
Some user interaction events provide positional meta-data about the event in relation to the browser's viewport. For example, if you highlight text, the Selection API reports the bounding box of the selected Ranges in relation to the viewport. In order for your application to react to such events, it's not uncommon to have to translate the reported viewport coordinates into element-local coordinates to render subsequent user interface components. To do this, we can use the Element.getBoundingClientRect() method and some simple math.
The Element.getBoundingClientRect() method reports the size and position of the contextual element relative to the browser's viewport. If we have other positional information that is also relative to the browser's viewport, we can calculate the element-local position by taking the difference between the two viewport-relative positions:
To see this in action, I've put together a simple demo that tracks mouse-clicks on the document (via event-delegation). It then takes the mouse-click viewport-coordinates and uses .getBoundingClientRect() to calculate the position of the click within a target element. The calculated Element-local position is then used to render a "dot" under the user's mouse.
As you can see, we're using event-delegation on the Document to listen for mouse-click events. We're then taking clicks that originate from within one of the boxes, and using the viewport-delta of the mouse coordinates and the box's bounding rectangle in order to append and position a "dot" element. And, when we run this code and click in one of the boxes, we get the following browser output:
The Element.getBoundingClientRect() method is an awesome feature that has standardized support in all major browsers, going back to IE9. In this case, you can see how easy it makes it to translate viewport-relative coordinates into element-local coordinates.
Want to use code from this post? Check out the license.
I find your solution pretty clever and straight forward. Nothing complicated really. Depends on what you are trying to achieve really. You said "forget about the click coordinates", But I find them I bit tricky depending on the situation. For example I have a knob that should rotate in certaic circle with some radius, no matter how far my touch, or cursor goes away from that circle I use trigonometry to calculate the new coordinates of the draggable knob. So in this case it is really important what points for reference you are going to use. I ended up with a knob that jumped immediately some degrees even if I didn't drag it at all. The mistake was that I didn't calculate correctly the reference point.
Dang, that sounds complicated! The second you have to pull "radii" into a conversation, my brain starts to melt. I was good with math up until Trigonometry. Geometry was probably my last good math level. Especially the proofs -- need to know why one angle is the same angle based on some combination of postulates and laws, that was fun! That's how I used to spend my homeroom period.
But, start to get rotations, sins, cosigns, tangents, sequences, series, differentials ... my brain goes HOLD UP SON! :P
That all said, part of why I said "forget about the mouse event" was because I had just recently played around with the Selection API:
... and, in that post, I looked at how the Selection API exposes a "bounding box" of the selected elements. That bounding box is the a DOMRect interface that is relative to the Viewport, just like the mouse coordinates. So, my only point was to say that there may be _other_ sources of coordinates beyond mouse events (such as the Selection API); and, I didn't want people to worry about why I chose event.clientX - which is Viewport relative - instead of event.pageX - which is Document relative.
Hope that makes sense.
Yep, make sense now. Thanks for the clarification.
Hi, your demo is perfect but is it possible to set transform:scale(1.2) property to box and then its possible to get perfect mouse click point coordinates ?
I don't have much experience with scaling via CSS. Are you suggesting that the
.getBoundingClientRect()method doesn't take
transforminto account when performing the calculation? If so, that would be surprising to me. I would assume that this approach should "just work", regardless of how the Element is being rendered.
I'm also encountering an issue when a parent element has a transform: scale applied to it. Any ideas how to take that data and modify the coordinates?
I have looking into finding the local click coordinates on an element where the parent has both
scaleapplied e.g. transform: translate(200px, 200px) scale(1.5);
Surprisingly, the MouseEvent.offsetX and offsetY were still local to the element and to the original scale - the target element was originally 250px height, and even thought he parent was scaled and transformed, clicking on the bottom edge of the target element gave me an offsetX of 250px (which I wasn't expecting)
This is weird stuff. I will have to do some R&D on the use of
scale. To be honest, I've never actually used
scalein my own work, so I am not really sure how it works. I'll do some playing around.