173

What's the easiest way to determine an elements position relative to the document/body/browser window?

Right now I'm using .offsetLeft/offsetTop, but this method only gives you the position relative to the parent element, so you need to determine how many parents to the body element, to know the position relaltive to the body/browser window/document position.

This method is also to cumbersome.

Kos
  • 4,890
  • 9
  • 38
  • 42
einstein
  • 13,389
  • 27
  • 80
  • 110

10 Answers10

219

You can get top and left without traversing DOM like this:

function getCoords(elem) { // crossbrowser version
    var box = elem.getBoundingClientRect();

    var body = document.body;
    var docEl = document.documentElement;

    var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
    var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

    var clientTop = docEl.clientTop || body.clientTop || 0;
    var clientLeft = docEl.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) };
}
Tieme
  • 62,602
  • 20
  • 102
  • 156
basil
  • 3,482
  • 2
  • 22
  • 20
  • 7
    This answer doesn't take into account the parents' offsets. It's only relative to the viewport – Nickey Sep 16 '16 at 12:09
  • 4
    @Nickey that's not true—the viewport position, plus the scroll offset checks, yield the coordinates relative to the whole document. – natchiketa Dec 21 '16 at 13:00
  • 1
    Can be buggy on mobile devices http://stackoverflow.com/a/32623832/962634 Need research – basil Jan 28 '17 at 06:30
  • 1
    Why you subtract clientTop/clientLeft? – Teemoh Jun 13 '17 at 04:22
  • 1
    @Teemoh clientTop / clientLeft corresponds to border values on the `` element. – Raphael Rafatpanah Mar 12 '18 at 19:15
  • 1
    Nonsense. Any number of ancestors can be scrolled. There is no way to compute the offset without traversing DOM. There is no SIMPLE way to compute the offset at all. Any solution that can do this RELIABLY is over 1kloc. – Szczepan Hołyszewski Aug 23 '21 at 09:54
  • @SzczepanHołyszewski I agree that DOM traversing is needed to handle all cases (e.g. margin:auto on `` element combined with `width: 50vmin` and multiple overflow:auto elements in the current DOM path). However, if you only need to support modern browsers (basically up-to-date Blink, Gecko and Webkit), you need maybe 20 lines of code. You do have to check `.offsetParent` recursively, though. – Mikko Rantalainen Nov 18 '22 at 14:58
202

You can use element.getBoundingClientRect() to retrieve element position relative to the viewport.

Then use document.documentElement.scrollTop to calculate the viewport offset.

The sum of the two will give the element position relative to the document:

element.getBoundingClientRect().top + document.documentElement.scrollTop
Kos
  • 4,890
  • 9
  • 38
  • 42
dy_
  • 6,462
  • 5
  • 24
  • 30
  • 14
    Relative to the viewport is not the same as relative to the document. If the page is scrolled down a bit then the the top relative to the viewport will be a smaller number than the top relative to the document. – MrVimes Jun 21 '14 at 12:20
  • 24
    he starts relative to the viewport and adds scrollY to get it relative to the document. – Joaquin Cuenca Abela Mar 20 '17 at 17:40
  • 7
    `document.documentElement.scrollTop` does not work in Safari, use `window.scrollY` instead – Fabian von Ellerts Apr 08 '19 at 11:43
  • 1
    getBoundingClientRect() calculates the bounding rectangle of the element and all its descendants. If the element has overflow: visible, it can have descendants outside of the element's layout rectangle, descendants BIGGER than itself, descendants arbitrarily displaced with a CSS transform, and a dozen other edge cases. – Szczepan Hołyszewski Aug 23 '21 at 09:57
  • awesome man, this helped to get the position relative to document of aggrid row. – pinarella Mar 15 '22 at 09:10
63

You can traverse the offsetParent up to the top level of the DOM.

function getOffsetLeft( elem )
{
    var offsetLeft = 0;
    do {
      if ( !isNaN( elem.offsetLeft ) )
      {
          offsetLeft += elem.offsetLeft;
      }
    } while( elem = elem.offsetParent );
    return offsetLeft;
}
Ry-
  • 218,210
  • 55
  • 464
  • 476
KeatsKelleher
  • 10,015
  • 4
  • 45
  • 52
  • 37
    No it does not, when any parent (especially html element!!!) has margins, paddings or borders. – Flash Thunder Nov 20 '14 at 15:10
  • 1
    regarding the margin stuff... it might help to set the box-sizing to border-box... or something along those lines... nothing that can't be fixed... –  Oct 20 '17 at 08:55
  • First,you need to decide whether to take account of border and margin according to box-sizing. Second, the collapsed margin should be considered. And last, there will be more complicated situation in the future which would make this answer worse. – xianshenglu Jun 06 '18 at 05:23
  • 11
    Why is this the accepted answer? This is outdated and poorly performing. Do yourself a favor and use getBoundingClientRect() as suggested in the other answers. – Fabian von Ellerts Jan 07 '19 at 15:05
  • it needs to be relative some element. only gets you the global offsetLeft. i see that is the question so I guess correct. I have another case which is relative to any other parent. i guess i can rewrite this. thanks! – mjs Oct 26 '20 at 11:42
15

I've found the following method to be the most reliable when dealing with edge cases that trip up offsetTop/offsetLeft.

function getPosition(element) {
    var clientRect = element.getBoundingClientRect();
    return {left: clientRect.left + document.body.scrollLeft,
            top: clientRect.top + document.body.scrollTop};
}
Robin Stewart
  • 3,147
  • 20
  • 29
8

document-offset (3rd-party script) is interesting and it seems to leverage approaches from the other answers here.

Example:

var offset = require('document-offset')
var target = document.getElementById('target')
console.log(offset(target))
// => {top: 69, left: 108} 
vsync
  • 118,978
  • 58
  • 307
  • 400
Mathieu M-Gosselin
  • 1,225
  • 1
  • 13
  • 17
7

I suggest using

element.getBoundingClientRect()

as proposed here instead of manual offset calculation through offsetLeft, offsetTop and offsetParent. as proposed here Under some circumstances* the manual traversal produces invalid results. See this Plunker: http://plnkr.co/pC8Kgj

*When element is inside of a scrollable parent with static (=default) positioning.

Community
  • 1
  • 1
HANiS
  • 530
  • 5
  • 9
  • You can make the offset calculation work also by incorporating scrollLeft and scrollTop into it. – Ben J Aug 18 '15 at 18:36
  • 1
    But I found `offsetLeft` and `offsetTop` don't take into account CSS3 transforms, which `getBoundingClientRect()` does. So that's a case the results may be invalid. If you need it, you can get an element's offset relative to parent (like `offsetLeft`) using `(element.getBoundingClientRect().left - parent.getBoundingClientRect().left)`. – Ben J Aug 18 '15 at 20:19
  • should def get higher up; these values are far more useful, and all in one call too! – mix3d Oct 01 '18 at 20:25
6

For those that want to get the x and y coordinates of various positions of an element, relative to the document.

const getCoords = (element, position) => {
  const { top, left, width, height } = element.getBoundingClientRect();
  let point;
  switch (position) {
    case "top left":
      point = {
        x: left + window.pageXOffset,
        y: top + window.pageYOffset
      };
      break;
    case "top center":
      point = {
        x: left + width / 2 + window.pageXOffset,
        y: top + window.pageYOffset
      };
      break;
    case "top right":
      point = {
        x: left + width + window.pageXOffset,
        y: top + window.pageYOffset
      };
      break;
    case "center left":
      point = {
        x: left + window.pageXOffset,
        y: top + height / 2 + window.pageYOffset
      };
      break;
    case "center":
      point = {
        x: left + width / 2 + window.pageXOffset,
        y: top + height / 2 + window.pageYOffset
      };
      break;
    case "center right":
      point = {
        x: left + width + window.pageXOffset,
        y: top + height / 2 + window.pageYOffset
      };
      break;
    case "bottom left":
      point = {
        x: left + window.pageXOffset,
        y: top + height + window.pageYOffset
      };
      break;
    case "bottom center":
      point = {
        x: left + width / 2 + window.pageXOffset,
        y: top + height + window.pageYOffset
      };
      break;
    case "bottom right":
      point = {
        x: left + width + window.pageXOffset,
        y: top + height + window.pageYOffset
      };
      break;
  }
  return point;
};

Usage

  • getCoords(document.querySelector('selector'), 'center')

  • getCoords(document.querySelector('selector'), 'bottom right')

  • getCoords(document.querySelector('selector'), 'top center')

Raphael Rafatpanah
  • 19,082
  • 25
  • 92
  • 158
2

If you don't mind using jQuery, then you can use offset() function. Refer to documentation if you want to read up more about this function.

Adam
  • 1,054
  • 1
  • 12
  • 26
2

http://www.quirksmode.org/js/findpos.html Explains the best way to do it, all in all, you are on the right track you have to find the offsets and traverse up the tree of parents.

Jake Kalstad
  • 2,045
  • 14
  • 20
0

The simple answer, which works in all modern browsers including Safari:

let x = element.getBoundingClientRect().left + window.scrollX
let y = element.getBoundingClientRect().top + window.scrollY
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27