264

I want to get the position of an element relative to the browser's viewport (the viewport in which the page is displayed, not the whole page). How can this be done in JavaScript?

Many thanks

Maximillian Laumeister
  • 19,884
  • 8
  • 59
  • 78
Waleed Eissa
  • 10,283
  • 16
  • 60
  • 82

13 Answers13

408

The existing answers are now outdated. The native getBoundingClientRect() method has been around for quite a while now, and does exactly what the question asks for. Plus it is supported across all browsers (including IE 5, it seems!)

From MDN page:

The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box, in pixels, with the top-left relative to the top-left of the viewport.

You use it like so:

var viewportOffset = el.getBoundingClientRect();
// these are relative to the viewport, i.e. the window
var top = viewportOffset.top;
var left = viewportOffset.left;
Denilson Sá Maia
  • 47,466
  • 33
  • 109
  • 111
Himanshu P
  • 9,586
  • 6
  • 37
  • 46
  • 4
    This may not work correctly when use browser zoom (Android Chrome). Solution from @rism ([see below](http://stackoverflow.com/a/1350681/2757230))works in this case – Dmitry Nov 08 '16 at 09:20
  • 7
    Good for most cases, but it assumes the element is not inside an embedded iframe, right? The question is asking about relative the browser's window. The el.getBoundingClientRect() will return relative the iframe's window, not the browser window. – cakidnyc Mar 23 '17 at 17:54
  • 16
    The question is asking about relative the browser's window. – Dmitry Dvornikov Mar 29 '17 at 12:16
  • 3
    @Denilson `getBoundingClientRect().top` is not in my IE11 it returns 0. Have you any solution. – Arif Jan 12 '18 at 14:47
  • @Arif: Sorry, but I cannot reproduce your issue. I tested this on IE11 and still got results: https://codepen.io/denilsonsa/pen/ypqyXj – Denilson Sá Maia Jan 14 '18 at 09:33
  • 2
    You simplify it a bit with `const { top, left } = viewportOffset` – David Ferreira Jun 29 '19 at 02:06
110

On my case, just to be safe regarding scrolling, I added the window.scroll to the equation:

var element = document.getElementById('myElement');
var topPos = element.getBoundingClientRect().top + window.scrollY;
var leftPos = element.getBoundingClientRect().left + window.scrollX;

That allows me to get the real relative position of element on document, even if it has been scrolled.

Fabio Nolasco
  • 7,122
  • 6
  • 35
  • 32
24
var element =  document.querySelector('selector');
var bodyRect = document.body.getBoundingClientRect(),
    elemRect = element.getBoundingClientRect(),
    offset   = elemRect.top - bodyRect.top;
Tautvydas
  • 1,268
  • 7
  • 19
  • 33
GURU PRASAD
  • 343
  • 4
  • 8
19

Edit: Add some code to account for the page scrolling.

function findPos(id) {
    var node = document.getElementById(id);     
    var curtop = 0;
    var curtopscroll = 0;
    if (node.offsetParent) {
        do {
            curtop += node.offsetTop;
            curtopscroll += node.offsetParent ? node.offsetParent.scrollTop : 0;
        } while (node = node.offsetParent);

        alert(curtop - curtopscroll);
    }
}

The id argument is the id of the element whose offset you want. Adapted from a quirksmode post.

Derek Swingley
  • 8,734
  • 5
  • 32
  • 33
  • 2
    Thanks for your answer but I think you misunderstood my question, I know how to get the top of an element relative to the page, what I'm trying to do is get the position relative to the 'viewport'(ie. the browser window, not the document) – Waleed Eissa Aug 29 '09 at 06:20
  • Sorry, your use of 'viewport' threw me. So, you're saying you want to account for the browser chrome, ie bookmarks toolbar, location bar, etc? – Derek Swingley Aug 29 '09 at 06:24
  • No, just the window through which you see the page. If the page is scrolled this will be different than the document top, otherwise they are the same. – Waleed Eissa Aug 29 '09 at 06:38
  • Oh OK, I understand. Not sure how to do that...will edit my answer if I come up with something. – Derek Swingley Aug 29 '09 at 06:46
  • I made the code above a little uglier but it works...use offsetParent.scrollTop – Derek Swingley Aug 29 '09 at 07:05
  • Thanks a lot. I see prototype uses parentNode instead of offsetParent for getting the scrollTop but I don't know the reason. – Waleed Eissa Aug 29 '09 at 08:18
13

jQuery implements this quite elegantly. If you look at the source for jQuery's offset, you'll find this is basically how it's implemented:

var rect = elem.getBoundingClientRect();
var win = elem.ownerDocument.defaultView;

return {
    top: rect.top + win.pageYOffset,
    left: rect.left + win.pageXOffset
};
nvioli
  • 4,137
  • 3
  • 22
  • 38
jordancooperman
  • 1,931
  • 2
  • 21
  • 33
9
function inViewport(element) {
    let bounds = element.getBoundingClientRect();
    let viewWidth = document.documentElement.clientWidth;
    let viewHeight = document.documentElement.clientHeight;

    if (bounds['left'] < 0) return false;
    if (bounds['top'] < 0) return false;
    if (bounds['right'] > viewWidth) return false;
    if (bounds['bottom'] > viewHeight) return false;

    return true;
}

source

Yotam
  • 10,295
  • 30
  • 88
  • 128
karlsebal
  • 1,449
  • 17
  • 23
5

The function on this page will return a rectangle with the top, left, height and width co ordinates of a passed element relative to the browser view port.

    localToGlobal: function( _el ) {
       var target = _el,
       target_width = target.offsetWidth,
       target_height = target.offsetHeight,
       target_left = target.offsetLeft,
       target_top = target.offsetTop,
       gleft = 0,
       gtop = 0,
       rect = {};

       var moonwalk = function( _parent ) {
        if (!!_parent) {
            gleft += _parent.offsetLeft;
            gtop += _parent.offsetTop;
            moonwalk( _parent.offsetParent );
        } else {
            return rect = {
            top: target.offsetTop + gtop,
            left: target.offsetLeft + gleft,
            bottom: (target.offsetTop + gtop) + target_height,
            right: (target.offsetLeft + gleft) + target_width
            };
        }
    };
        moonwalk( target.offsetParent );
        return rect;
}
rism
  • 11,932
  • 16
  • 76
  • 116
4

You can try:

node.offsetTop - window.scrollY

It works on Opera with viewport meta tag defined.

Szere Dyeri
  • 14,916
  • 11
  • 39
  • 42
  • 12
    If I understand [the documentation](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop) correctly, `offsetTop` is relative to the closest positioned element. Which, depending on the page structure, may or may not be root element. – Denilson Sá Maia Feb 02 '17 at 09:17
3

I am assuming an element having an id of btn1 exists in the web page, and also that jQuery is included. This has worked across all modern browsers of Chrome, FireFox, IE >=9 and Edge. jQuery is only being used to determine the position relative to document.

var screenRelativeTop =  $("#btn1").offset().top - (window.scrollY || 
                                            window.pageYOffset || document.body.scrollTop);

var screenRelativeLeft =  $("#btn1").offset().left - (window.scrollX ||
                                           window.pageXOffset || document.body.scrollLeft);
Jonathan Soifer
  • 2,715
  • 6
  • 27
  • 50
Sunil
  • 20,653
  • 28
  • 112
  • 197
2

Thanks for all the answers. It seems Prototype already has a function that does this (the page() function). By viewing the source code of the function, I found that it first calculates the element offset position relative to the page (i.e. the document top), then subtracts the scrollTop from that. See the source code of prototype for more details.

Waleed Eissa
  • 10,283
  • 16
  • 60
  • 82
  • Good decision. It's probably best to go with one of the major libraries as they're more likely to give the same result across browsers. If you're curious, check my my answer. The code there will do this but I have no idea how it will hold up across browsers. – Derek Swingley Aug 29 '09 at 08:15
2

Sometimes getBoundingClientRect() object's property value shows 0 for IE. In that case you have to set display = 'block' for the element. You can use below code for all browser to get offset.

Extend jQuery functionality :

(function($) {
    jQuery.fn.weOffset = function () {
        var de = document.documentElement;
        $(this).css("display", "block");
        var box = $(this).get(0).getBoundingClientRect();
        var top = box.top + window.pageYOffset - de.clientTop;
        var left = box.left + window.pageXOffset - de.clientLeft;
        return { top: top, left: left };
    };
}(jQuery));

Use :

var elementOffset = $("#" + elementId).weOffset();
Arif
  • 6,094
  • 4
  • 49
  • 81
1

Based on Derek's answer.

/**
 * Gets element's x position relative to the visible viewport.
 */
function getAbsoluteOffsetLeft(el) {
  let offset = 0;
  let currentElement = el;

  while (currentElement !== null) {
    offset += currentElement.offsetLeft;
    offset -= currentElement.scrollLeft;
    currentElement = currentElement.offsetParent;
  }

  return offset;
}

/**
 * Gets element's y position relative to the visible viewport.
 */
function getAbsoluteOffsetTop(el) {
  let offset = 0;
  let currentElement = el;

  while (currentElement !== null) {
    offset += currentElement.offsetTop;
    offset -= currentElement.scrollTop;
    currentElement = currentElement.offsetParent;
  }

  return offset;
}
Maciej Krawczyk
  • 14,825
  • 5
  • 55
  • 67
1

Here is something for Angular2 +. Tested on version 13

event.srcElement.getBoundingClientRect().top;
Rohit Sharma
  • 3,304
  • 2
  • 19
  • 34
Johansrk
  • 5,160
  • 3
  • 38
  • 37
  • What if the element has transform translate or some of its parents, getBoundingClienRect() won't take that in consideration, any idea? – Ammar Mourad Apr 21 '23 at 21:20