87

I want to have the exact distance between the Y-coordinate of an element an the Y-value=0, which I consider as the top of the document.

myElement.getBoundingClientRect().top;

But the value of getBoundingClientRect() seems to change while scrolling. How can I get the real distance between myElement and the Y-coordinate=0 (top of document)?

Carmine Tambascia
  • 1,628
  • 2
  • 18
  • 32
enzian
  • 1,607
  • 3
  • 18
  • 19
  • I would probably close as a dupe of [This question](http://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element), but why are you using `getBoundingClientRect` and not any of the usual ways to get the offset position relative to the document ? – adeneo Sep 02 '14 at 18:40
  • @adeneo Which other ways are there? – enzian Sep 02 '14 at 18:43
  • See the question above, or look at what jQuery does in `offset` – adeneo Sep 02 '14 at 18:51
  • for a text node, this is the only solution since `offset()` isn't available. – ThePhi Sep 17 '18 at 11:30

2 Answers2

140

It is because getBoundingClientRect() gets values with respect to the window(only the current visible portion of the page), not the document(whole page).
Hence, it also takes scrolling into account when calculating its values
Basically, document = window + scroll

So, to get the distance between myElement and the Y-coordinate=0 (top of document), you would have add the value of vertical-scroll also:

myElement.getBoundingClientRect().top + window.scrollY;

Source: https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect

n4m31ess_c0d3r
  • 3,028
  • 5
  • 26
  • 35
  • 5
    Your solution is broken for Android Chrome 43, and I think broken if using pinch-zooming on Internet Explorer (my fix was made a long time ago to fix a problem doing it your way). – robocat Sep 17 '15 at 06:38
36

getBoundingClientRect needs a bit more care to avoid bugs in scrollY/pageYOffset:

function absolutePosition(el) {
    var
        found,
        left = 0,
        top = 0,
        width = 0,
        height = 0,
        offsetBase = absolutePosition.offsetBase;
    if (!offsetBase && document.body) {
        offsetBase = absolutePosition.offsetBase = document.createElement('div');
        offsetBase.style.cssText = 'position:absolute;left:0;top:0';
        document.body.appendChild(offsetBase);
    }
    if (el && el.ownerDocument === document && 'getBoundingClientRect' in el && offsetBase) {
        var boundingRect = el.getBoundingClientRect();
        var baseRect = offsetBase.getBoundingClientRect();
        found = true;
        left = boundingRect.left - baseRect.left;
        top = boundingRect.top - baseRect.top;
        width = boundingRect.right - boundingRect.left;
        height = boundingRect.bottom - boundingRect.top;
    }
    return {
        found: found,
        left: left,
        top: top,
        width: width,
        height: height,
        right: left + width,
        bottom: top + height
    };
}

The bugs to avoid are:

  • scrolling in Android Chrome since Chrome Mobile 43 has wrong values for scrollY/pageYOffset (especially when the keyboard is showing and you scroll).

  • Pinch-zoom in Microsoft IE or Edge causes wrong values for scrollY/pageYOffset.

  • Some (obsolete) browsers don't have a height/width e.g. IE8

Edit: The above code can be simplified a lot by just using document.body.getBoundingClientRect() instead of adding a div - I haven't tried it though so I am leaving my answer as it stands. Also the body needs margin:0 (reset.css usually does this). This answer simplifies the code down a lot, while still avoiding the bugs in jQuery.offset()!

Edit 2: Chrome 61 introduced window.visualViewport to give correct values for the actual viewport which is probably another way to fix issues; but beware that Android Chrome 66 was still buggy if Settings -> Accessability -> Force enable zoom was ticked (bugs with orientation change, focused inputs, absolutely positioned popup wider than viewport).

robocat
  • 5,293
  • 48
  • 65
  • 1
    Perfect answer. In my case, the document.body.getBoundingClientRect() returned wrong values but this answer worked. – Moradnejad Apr 24 '17 at 08:00
  • What is this? `offsetBase = absolutePosition.offsetBase;` absolutePosition is not defined right there... – Guntram May 16 '18 at 15:22
  • @Guntram `absolutePosition` is defined - it is the name of the function (function object) on the first line. This is a 'javascripty' way to store a global variable without adding another global identifier. I admit it shouldn't pass a code review, and the code presumes that the absolutePosition function is defined at the global level. My edit suggests you use document.body as a tidier solution (althought that presumes margin is set to zero on ). Also see jQuery $() which is a function that has member properties and member functions. – robocat Jun 07 '18 at 02:41