- Coordinate systems
- Element coordinates by
offsetParent
- The right way:
elem.getBoundingClientRect
- The combined approach
- Summary
There are two coordinate systems in the browser.
- relative to
document
- the zero point is at the left-upper corner of the page. - relative to
window
- the zero point is at the left-upper corner of the current visible area.
Coordinate systems
When the page is not scrolled, window and document coordinates are the same and share the zero point:
After the scroll, the visible area moves from the document start:
Actually, it is easy to transform between these coordinate systems. Document coordinates are window coordinates plus scroll.
Most of time, only document coordinates are used, because they remain same after scrolling.
Element coordinates by offsetParent
Element coordinates are the coordinates of the left-upper corner. There is unfortunately no single property which gives coordinates. But they can be calculated using offsetTop/offsetLeft
and offsetParent
.
A natural (but as we’ll see, a buggy) way of calculating absolute coordinates is to traverse up the offsetParent
chain and sum offsetLeft/offsetTop
, like this:
function getOffsetSum(elem) { var top=0, left=0 while(elem) { top = top + parseInt(elem.offsetTop) left = left + parseInt(elem.offsetLeft) elem = elem.offsetParent } return {top: top, left: left} }
There are two main downsides of this approach.
- It is buggy. Different browsers have different pitfalls. There are problems with taking borders and scrolls into account.
- It is slow. Every time we have to go through whole chain of
offsetParents
.
It is possible to write a cross-browser code with all the bugs fixed, but let’s review an alternative solution which is supported by Internet Explorer 6+, Firefox 3+ и Opera 9.62+, and modern Safari/Chrome too.
The right way: elem.getBoundingClientRect
This method is described in W3C standard, and most modern browsers implement it (IE too).
It returns a rectangle which encloses the element. The rectangle is given as an object with properties top, left, right, bottom
.
The four numbers represent coordinates of the top-left and right-bottom corners. For example, click on the button below to see it’s rectangle:
<input id="brTest" type="button" value="Show button.getBoundingClientRect()" onclick='showRect(this)'/> <script> function showRect(elem) { var r = elem.getBoundingClientRect() alert("Top/Left: "+r.top+" / "+r.left) alert("Right/Bottom: "+r.right+" / "+r.bottom) } </script>**The coordinates are given relative to `window`, not the document**. For example, if you scroll this page, so that the button goes to the window top, then its `top` coordinate becomes close to `0`, because it is given relative to window. To calculate coordinates relative to the document that, we need to take page scroll into account.
function getOffsetRect(elem) { // (1) var box = elem.getBoundingClientRect() var body = document.body var docElem = document.documentElement // (2) var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft // (3) var clientTop = docElem.clientTop || body.clientTop || 0 var clientLeft = docElem.clientLeft || body.clientLeft || 0 // (4) var top = box.top + scrollTop - clientTop var left = box.left + scrollLeft - clientLeft return { top: Math.round(top), left: Math.round(left) } }The steps are:
- Get the enclosing rectangle.
- Calculate the page scroll. All browsers except IE<9 support `pageXOffset/pageYOffset`, and in IE when DOCTYPE is set, the scroll can be taken from
documentElement(<html>)
, otherwise from `body` - so we take what we can. - The document (`html` or `body`) can be shifted from left-upper corner in IE. Get the shift.
- Add scrolls to window-relative coordinates and substract the shift of `html/body` to get coordinates in the whole document.
Click anywhere on the yellow div. It will show results for getOffsetSum(elem)
and getOffsetRect(elem)
below. Note they usually don’t match.
To see which result is correct - click on the very upper-left corner of the yellow element. It is located on the upper-left corner of the black border.
The absolute mouse coordinates will appear so you can compare them with getOffsetSum/getOffsetRect
.
Try it to see that getOffsetRect
is always right .
The combined approach
Many frameworks use something a combined approach:
function getOffset(elem) { if (elem.getBoundingClientRect) { return getOffsetRect(elem) } else { // old browser return getOffsetSum(elem) } }
function getOffsetSum(elem) { var top=0, left=0 while(elem) { top = top + parseInt(elem.offsetTop) left = left + parseInt(elem.offsetLeft) elem = elem.offsetParent } return {top: top, left: left} } function getOffsetRect(elem) { var box = elem.getBoundingClientRect() var body = document.body var docElem = document.documentElement var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft var clientTop = docElem.clientTop || body.clientTop || 0 var clientLeft = docElem.clientLeft || body.clientLeft || 0 var top = box.top + scrollTop - clientTop var left = box.left + scrollLeft - clientLeft return { top: Math.round(top), left: Math.round(left) } } function getOffset(elem) { if (elem.getBoundingClientRect) { return getOffsetRect(elem) } else { return getOffsetSum(elem) } }
Summary
There are document-based and window-based coordinates. Document-based are scroll-tolerant and are used most of time.
The two methods to calculate coordinates are:
- Sum
offsetLeft/Top
- many browser bugs, not recommended. - Use
getBoundingClientRect
- works in all recent major browsers, also supported by IE6+.