Positioning with Mouse Events, Offset, getBoundingClientRect, and getComputedStyle
On a recent project, I was using event listeners on mouse events. I was trying to determine where on some elements I had clicked, as a percentage in relation to their width. During the process I came across a couple different ways to solve the problem, using different methods provided by JavaScript. I wanted to list some basic information about the different methods and when one might be more useful than the other.
So I had an event triggered from the element I clicked on. The MouseEvent provides us with several ways of determining where we clicked. Here’s a quick rundown:
MouseEvent.clientX
/MouseEvent.clientY
~ Coordinates of mouse event relative to viewport. Calculate from the left side and the top of the viewport.MouseEvent.movementX
/MouseEvent.movementY
~ Coordinates of mouse event relative to last mouse event.MouseEvent.offsetX
/MouseEvent.offsetY
~ Coordinates of mouse event relative to the edge of the padding on the targeted node. Calculated from the left padding and the top padding.MouseEvent.pageX
/MouseEvent.pageY
~ Coordinates of mouse event relative to the entire document. Calculated from left and top sides of entire document, even if scrolled out of view.MouseEvent.screenX
/MouseEvent.screenY
~ Coordinates of mouse event relative to the user’s entire screen. Calculated from the left and top sides of screen. If multiple screens are being used, value is calculated from the farthest left, and uppermost screens respectively.
This gives us a lot of flexibility when handling mouse events. For my use case, I was trying to determine the mouse’s horizontal position inside a specific element. I went with the MouseEvent.offsetX option, as it would give me the position relative to the element the event listener was attached to.
Now I just had to figure out the width of the element I had clicked on, and calculate the percentage based on how far into the element I clicked. Some ways we can do this, are with the HTMLElements API’s offset properties, with the Element class’ getBoundingClientRect method, or with the Window interfaces’ getComputedStyle method. Each gives you some slightly different information, and therefore one may fit your use case better than another.
HTMLElements have a number of offset properties associated with them. In my case, HTMLElement.offsetWidth
gave me the width property I needed to determine how far into a div I had clicked, and it was easy to calculate my horizontal percentage by dividing my offsetX value by my elements width, and multiplying by 100. But maybe you need more options. Here is a quick rundown of some of the HTMLElement offset properties.
HTMLElement.offsetHeight
~ Returns height of element, including padding and borders. Does not include any pseudo elements, such as ::before or ::after.HTMLElement.offsetWidth
~ Returns width of element, including padding and borders. Does not include any pseudo elements, such as ::before or ::after.HTMLElement.offsetTop
~ Returns the distance between the outside of the current elements’ top border to the inside of its parent node’s top border.HTMLElement.offsetLeft
~ Returns the distance between the outside of the current elements’ left border to the inside of its parent node’s left border.
For determining positioning, the offsetTop and offsetLeft methods can be useful, but it’s important to know what the output is based on. The offset position number returned is the number of pixels from the edge of the selected element, to the edge of the selected elements’ parent element, and the parent element can change depending on your code.
HTML.offsetParent
~ The closest parent element that has been positioned, as long as that position is not:static
,initial
that reverts tostatic
,revert
that points to eitherstatic
orinitial
as previously defined,unset
that defaults toinitial
, ornone
. All other positioned elements can be an offsetParent. If no parents have been positioned, it’s the closesttd
,th
, ortable
element. If none of those exist, the parent reverts to thebody
element.
Another way for us to determine positioning is with the Element class’ Element.getBoundingClientRect()
method. Calling this method on an element returns to you an object containing a number of different properties. Here however, you are returned position values relative to the viewport, instead of the document. The properties the getBoundingClientRect() object contains are:
top
/bottom
/left
/right
/x
/y
~Each property contains the distance between the outer edge of its border and the edge of the viewport. This distance is only calculated from the left and top sides of the viewport. So the left value plus the width of the element equals the right value, and the top value plus the height of the element equals the bottom value. x and y hold the same values as left and top.height
/width
~ Contains height / width of element, including padding and borders. Does not include any pseudo elements, such as ::before or ::after.
If you need to do calculations based on the viewport instead of the element’s position on the page, or if something is preventing you from using an offset property this might be a good option for you. It’s worth noting that getBoundingClientRect()
is not as performant as something like offsetX
. It likely won’t make a huge difference in most situations, but it’s good to remember for when it does matter.
Another option for getting the width of an element is the Window interface’s getComputedStyle() method:
getComputedStyle(element, pseudoElement)
~ Returns an object containing all the css properties currently applied to that object. Pseudo element is optional.
This route is great if you need to measure any pseudo elements along with your element, as the other height and width methods don’t account for this. If all you need however is just the width or height of one element, just using an offset property would likely read cleaner.
These were just some interesting ways I found to determine positioning, and there are undoubtedly many more. It’s nice to have such a range of options available for different situations. I highly recommend playing around with each method to get a good handle on the data available for each.