Get position/offset of element relative to a parent container?
Asked Answered
W

7

215

How can I retrieve the offset of a container relative to a parent with pure JS?

Wargo answered 24/7, 2012 at 15:58 Comment(0)
P
294

element.offsetLeft and element.offsetTop give an element's position with respect to its offsetParent (which is the nearest parent element with a position of relative or absolute.)

Picayune answered 24/7, 2012 at 16:2 Comment(7)
Element's positioned static are not offset parents.Carryingon
don't use semicolons inside braces, use commasDifferentiation
what about position fixed? are they offset parents?Phenacetin
Yes, @Ayya- position fixed also creates a new offset contextEndomorphism
please correct the answer since Zepto was a short-lived joke and jQuery is still and forever the king.Belomancy
@Belomancy jQuery is not the king of anything since ie9 reached EOL in January 2016, we have a standardised DOM selection in all major browsers, learn pure js. Also opinions about what framework to use have no place on SO, since it heavily depends on the project and target platform.Quite
Seems it always returns 0 if parent element scrolls? e.g. has overflow: scroll or overflow: auto ?Borrow
J
111

in pure js just use offsetLeft and offsetTop properties.
Example fiddle: http://jsfiddle.net/WKZ8P/

var elm = document.querySelector('span');
console.log(elm.offsetLeft, elm.offsetTop);
p   { position:relative; left:10px; top:85px; border:1px solid blue; }
span{ position:relative; left:30px; top:35px; border:1px solid red; }
<p>
    <span>paragraph</span>
</p>
Jacobin answered 24/7, 2012 at 16:2 Comment(6)
Thank you. Is there also a way to get the offset to a parent.parent element?Wargo
p.offsetTop + p.parentNode.offsetTop You could wrap this in a recursive function call much like PPK proposed a few years ago. You could alter the function to either pass the number of levels to go up or use a selector to mimic $(selector).parents(parentSelector). I would prefer this solution because the zepto implementation only works on browsers supporting getBoundingClientsRect - which some mobiles don't.Perice
I thought getBoundingClientsRect was widely supported... if anyone knows which mobiles do not support it I would be interested (can't find any support table for mobiles about it).Gorgias
This is probably the most correct answer even though it's not marked. My personal solution was changing $(this).offset().left to this.offsetLeft.Espy
This also fixes wrong jQuery.position() behavior inside of a 3d transformed parent, thank you so much!Tedman
-1 , It works relative to "offsetParent"(developer.mozilla.org/en-US/docs/Web/API/HTMLElement/…) which is NOT always the parent element, so this method won't work if the parent element is not positioned.Sachi
B
7

I did it like this in Internet Explorer.

function getWindowRelativeOffset(parentWindow, elem) {
    var offset = {
        left : 0,
        top : 0
    };

    // relative to the target field's document
    offset.left = elem.getBoundingClientRect().left;
    offset.top = elem.getBoundingClientRect().top;

    // now we will calculate according to the current document, this current
    // document might be same as the document of target field or it may be
    // parent of the document of the target field
    var childWindow = elem.document.frames.window;
    while (childWindow != parentWindow) {
        offset.left = offset.left + childWindow.frameElement.getBoundingClientRect().left;
        offset.top = offset.top + childWindow.frameElement.getBoundingClientRect().top;
        childWindow = childWindow.parent;
    }

    return offset;
};

=================== you can call it like this

getWindowRelativeOffset(top, inputElement);

I focus on IE only as per my focus but similar things can be done for other browsers.

Bunker answered 8/10, 2013 at 8:15 Comment(1)
It throws error for me on elem.document.frames says documents is undefinedPogrom
O
4

Add the offset of the event to the parent element offset to get the absolute offset position of the event.

An example :

HTMLElement.addEventListener('mousedown',function(e){

    var offsetX = e.offsetX;
    var offsetY = e.offsetY;

    if( e.target != this ){ // 'this' is our HTMLElement

        offsetX = e.target.offsetLeft + e.offsetX;
        offsetY = e.target.offsetTop + e.offsetY;

    }
}

When the event target is not the element which the event was registered to, it adds the offset of the parent to the current event offset in order to calculate the "Absolute" offset value.

According to Mozilla Web API: "The HTMLElement.offsetLeft read-only property returns the number of pixels that the upper left corner of the current element is offset to the left within the HTMLElement.offsetParent node."

This mostly happens when you registered an event on a parent which is containing several more children, for example: a button with an inner icon or text span, an li element with inner spans. etc...

Ortega answered 18/1, 2017 at 20:41 Comment(2)
What about the parent of the parent? What about nested elements having scrollbars? What about previous elements with display:none? A true document offset position is almost impossible to calculate, yet the rendering engine can know it. Too bad simple properties like document position were never included in DOM.Bumper
@DavidSpector wordOrtega
T
2

Example

So, if we had a child element with an id of "child-element" and we wanted to get it's left/top position relative to a parent element, say a div that had a class of "item-parent", we'd use this code.

var position = $("#child-element").offsetRelative("div.item-parent");
alert('left: '+position.left+', top: '+position.top);

Plugin Finally, for the actual plugin (with a few notes explaining what's going on):

// offsetRelative (or, if you prefer, positionRelative)
(function($){
    $.fn.offsetRelative = function(top){
        var $this = $(this);
        var $parent = $this.offsetParent();
        var offset = $this.position();
        if(!top) return offset; // Didn't pass a 'top' element 
        else if($parent.get(0).tagName == "BODY") return offset; // Reached top of document
        else if($(top,$parent).length) return offset; // Parent element contains the 'top' element we want the offset to be relative to 
        else if($parent[0] == $(top)[0]) return offset; // Reached the 'top' element we want the offset to be relative to 
        else { // Get parent's relative offset
            var parent_offset = $parent.offsetRelative(top);
            offset.top += parent_offset.top;
            offset.left += parent_offset.left;
            return offset;
        }
    };

    $.fn.positionRelative = function(top){
        return $(this).offsetRelative(top);
    };
}(jQuery));

Note : You can Use this on mouseClick or mouseover Event

$(this).offsetRelative("div.item-parent");
Thermolabile answered 12/6, 2017 at 7:34 Comment(4)
I don't see scrolling accounted for. There are lots of odd cases that can happen.Bumper
jquery.min.js:2 Uncaught RangeError: Maximum call stack size exceeded this is the error it throwingFieldpiece
How is this pure JavaScript as requested in the question?Tinnitus
This was exactly what I needed for a fixed form with no scrolling.Dumbwaiter
T
2

I got another Solution. Subtract parent property value from child property value

$('child-div').offset().top - $('parent-div').offset().top;
Thermolabile answered 12/6, 2017 at 13:21 Comment(1)
How is this pure JavaScript as requested in the question?Tinnitus
D
0

Sure is easy with pure JS, just do this, work for fixed and animated HTML 5 panels too, i made and try this code and it works for any brower (include IE 8):

<script type="text/javascript">
    function fGetCSSProperty(s, e) {
        try { return s.currentStyle ? s.currentStyle[e] : window.getComputedStyle(s)[e]; }
        catch (x) { return null; } 
    }
    function fGetOffSetParent(s) {
        var a = s.offsetParent || document.body;

        while (a && a.tagName && a != document.body && fGetCSSProperty(a, 'position') == 'static')
            a = a.offsetParent;
        return a;
    }
    function GetPosition(s) {
        var b = fGetOffSetParent(s);

        return { Left: (b.offsetLeft + s.offsetLeft), Top: (b.offsetTop + s.offsetTop) };
    }    
</script>
Dreyer answered 29/3, 2017 at 22:17 Comment(1)
Did you test with all possible cases of parent nodes and element properties? Does this account for scrolling and nested scrolling?Bumper

© 2022 - 2024 — McMap. All rights reserved.