Skip to content

Instantly share code, notes, and snippets.

@jlong
Last active Mar 24, 2021
Embed
What would you like to do?
Get the offset of an element relative to the viewport and take scrolling and borders into account
function getViewportOffset(element) {
var node = element
, left = node.offsetLeft
, top = node.offsetTop
;
node = node.parentNode;
do {
var styles = getComputedStyle(node);
if (styles) {
var position = styles.getPropertyValue('position');
left -= node.scrollLeft;
top -= node.scrollTop;
if (/relative|absolute|fixed/.test(position)) {
left += parseInt(styles.getPropertyValue('border-left-width'), 10);
top += parseInt(styles.getPropertyValue('border-top-width'), 10);
left += node.offsetLeft;
top += node.offsetTop;
}
node = position === 'fixed' ? null : node.parentNode;
} else {
node = node.parentNode;
}
} while (node);
return { left: left, top: top };
}
@eladkarako

This comment has been minimized.

Copy link

@eladkarako eladkarako commented Jun 22, 2019

looks fine at first,
but sometimes it goes up the DOM-tree up too far (the document-object, [above document.documentElement]) which can not be used with getComputedStyle,
that will throw an error of Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'. ,
The second thing I've discovered is that sometimes scrollLeft and/or scrollTop are undefined, which will turn your collected left and top values to NaN...

The following is pretty much the code above, but with those, and several other fixes (plus, I too love 'comma-first' syntax :])

The code is overly explicit, and commented-in on several issues I've thought it is best to explain,
especially for myself in-case I would need to use it some time from now...

function get_offset_of_element_in_viewport(element){ "use strict";
  var offset = {"left":0,"top":0};  //keep initial values number so you won't get NaN on first math. operation (on undefined).

  for( ; null !== element ; ){
    var style     = undefined
       ,position  = undefined
       ;

    offset.left = offset.left + element.offsetLeft;
    offset.top  = offset.top  + element.offsetTop;

    try{
    style = self.getComputedStyle(element);
    }catch(err){}
    if("undefined" === typeof style) return offset;      //sometimes we can go up to the document (not document.documentElement a.k.a HTML-tag but the actual document-object - then getComputedStyle will fail.

    if(null === element.parentNode)  return offset;      //element is-not contained.

    element  = element.parentNode;                      //element is contained. also - that's our "loop++".
    offset.left = offset.left - (element.scrollLeft || 0);  //sometimes scrollLeft is undefined (that will prevent NaN in the offset.left).
    offset.top  = offset.top  - (element.scrollTop  || 0);  //sometimes scrollLeft is undefined (that will prevent NaN in the offset.top).
    
    position = style.getPropertyValue("position");
    if(true === /relative|absolute|fixed/i.test(position)){
      offset.left = offset.left + (Number.parseInt(style.getPropertyValue("border-left-width"), 10) || 0);
      offset.top  = offset.top  + (Number.parseInt(style.getPropertyValue("border-top-width"),  10) || 0);
    }
    
    element = (true === /fixed/i.test(position)) ? null : element.parentNode; //skip going-up more, in-case the element has a fixed position, in-that-case we're done (the loop stop-condition will be activated).
  }
  
  return offset;
}


console.log(
  get_offset_of_element_in_viewport(
    document.querySelector('input[class="label-input-label"]')   //in test page:  https://closure-compiler.appspot.com/home
  )
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment