Finding the real position of an element with respect to the document

The first attempt when trying to get the position of an element with respect to the document has been to use the getBoundingClientRect method.

Element.getBoundingClientRect and Element.getClientRects

These methods returns the size of the element and the position of it with respect to the viewport, returning the values of top, bottom, left, right width and height. Basically we are going to get different results depending on the position of the viewport and the scroll position of the document.

What is the problem whit these methods?
Simply: if the page scrolls and we are looking for an element not inside the page we’ll get different numbers.
Consider the following HTML:


<div style="margin-top: 300vh"><p>Just to add some scrolling</p></div>
<img src="https://drscdn.500px.org/photo/96780165/m%3D2048/206145db9ef105c12898752e002b0400" class="image"/>

And run the following code:


var image = document.querySelector('img');
var posBeforeScroll = image.getBoundingClientRect();
console.log('before ', posBeforeScroll);
window.scroll(0, 3000);
var posAfterScroll = image.getBoundingClientRect();
console.log('after ', posAfterScroll);

What is going to happen is that you are going to get different numbers in respect to the fact that the document is scrolled or not. I made a pen for this:

See the Pen boundRect and scroll by Maurizio (@napy84) on CodePen.

Getting the position without using the boundingClientRect method

So we saw how using getBoundClientRect is not always ideal, even it saved my ass in a very large number of occasions and even John R. has a blog post about it. In my case (I have to place an element on top on another), it is not good. What other properties the CSSOM offers me here?

We have the following properties:

HTMLElement.offsetParent
Returns the closest element containing the HTMLElement with a positioning property.
HTMLElement.offsetLeft
Returns the distance in between the upper-left corner of the element and the HTMLElement.offsetParent element.
HTMLElement.offsetTop
Returns the distance in between the top of the element and the HTMLElement.offsetParent element.

How can I use it to get the position of an element within the document?

I will need to climb up the dom, across parent elements to find out. The HTMLElement.offsetParent will return null in the following situations:

  • The element has for example a display: none; property and therefore doesn’t have a CSS layout box.
  • The element is the root element of the structure
  • The element is the HTML Body element
  • The element has set position: fixed

It will return an element if:

  • Climbing the DOM tree, the element doesn’t have a position: static
  • If the element is position: static but is part of a td, th, table

Therefore to find the correct visual position of an element inside the document tree, we need to climb up the DOM going by offsetParent elements.

Given an element we want to find the top and left position using the offsetParent, offsetTop and offsetLeft properties.


function climbUp (element) {
  var a = element;
  var properties = {
    top: 0,
    left: 0
  };
  while (a) {
    properties.top += a.offsetTop;
    properties.left += a.offsetLeft;
    a = a.offsetParent;
  }

  return properties;
}

This is a demo I made to show it:

See the Pen BoeLXM by Maurizio (@napy84) on CodePen.