offset vs getBoundingClientRect() (v7)

Revision 7 of this benchmark created on


Preparation HTML

<div style="position:relative;padding:20px;margin-top:12px">
  <div style="position:relative;padding:40px;margin-top:12px">
    <div id="mydiv">
      test
      <div>
      </div>
    </div>
    <script src="http://code.jquery.com/jquery-2.0.0.min.js">
    </script>

Setup

var mydiv = document.getElementById('mydiv')
  var $mydiv = $(mydiv);
  
  function boundOffset(elem) {
    var box = elem.getBoundingClientRect();
    return {
      top: box.top + (window.pageYOffset || document.documentElement.scrollTop) - (document.documentElement.clientTop || 0),
      left: box.left + (window.pageXOffset || document.documentElement.scrollLeft) - (document.documentElement.clientLeft || 0)
    };
  }
  
  var loopedOffset = function(elem) {
      var offsetLeft = elem.offsetLeft,
          offsetTop = elem.offsetTop;
      while (elem = elem.offsetParent) {
        offsetLeft += elem.offsetLeft;
        offsetTop += elem.offsetTop;
      }
      return {
        left: offsetLeft,
        top: offsetTop
      };
      };
  
  var loopedOffsetWithFixedCheck = function(elem) {
      var offsetLeft = elem.offsetLeft,
          offsetTop = elem.offsetTop;
      while (elem = elem.offsetParent) {
        offsetLeft += elem.offsetLeft;
        offsetTop += elem.offsetTop;
        if (elem.style.position === 'fixed') {
          offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
          offsetTop += window.pageYOffset || document.documentElement.scrollTop;
        }
      }
      return {
        left: offsetLeft,
        top: offsetTop
      };
      }
      
      
      
  var loopedOffsetOptimized = function(elem) {
      var offsetLeft = elem.offsetLeft,
          offsetTop = elem.offsetTop,
          lastElem = elem;
      while (elem = elem.offsetParent) {
        offsetLeft += elem.offsetLeft;
        offsetTop += elem.offsetTop;
        lastElem = elem;
      }
      if (lastElem && lastElem.style.position === 'fixed') {
        offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
        offsetTop += window.pageYOffset || document.documentElement.scrollTop;
      }
      return {
        left: offsetLeft,
        top: offsetTop
      };
      };
  
  var loopedOffsetOptimized2 = function (elem) {
    var offsetLeft = elem.offsetLeft
      , offsetTop = elem.offsetTop
      , lastElem = elem;
  
    while (elem = elem.offsetParent) {
      if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0
        break;
      }
      offsetLeft += elem.offsetLeft;
      offsetTop += elem.offsetTop;
      lastElem = elem;
    }
    if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
      //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
      offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
      offsetTop += window.pageYOffset || document.documentElement.scrollTop;
    }
    return {
      left: offsetLeft,
      top: offsetTop
    };
  };
  
  var body = document.body;
  var loopedOffsetOptimized3 = function (elem) {
    var offsetLeft = elem.offsetLeft
      , offsetTop = elem.offsetTop
      , lastElem = elem;
  
    while (elem = elem.offsetParent) {
      if (elem === body) { //from my observation, document.body always has scrollLeft/scrollTop == 0
        break;
      }
      offsetLeft += elem.offsetLeft;
      offsetTop += elem.offsetTop;
      lastElem = elem;
    }
    if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6
      //if(lastElem !== document.body) { //faster but does gives false positive in Firefox
      offsetLeft += window.pageXOffset || document.documentElement.scrollLeft;
      offsetTop += window.pageYOffset || document.documentElement.scrollTop;
    }
    return {
      left: offsetLeft,
      top: offsetTop
    };
  };

Test runner

Ready to run.

Testing in
TestOps/sec
loopedOffset
loopedOffset(mydiv).top
ready
$.offset
$mydiv.offset().top
ready
boundOffset
//essentialy this is stripped out from jQuery
boundOffset(mydiv).top
ready
loopedOffsetWithFixedCheck
//makes it compatible with $.offset when element has position: fixed
loopedOffsetWithFixedCheck(mydiv).top
ready
loopedOffsetOptimized
loopedOffsetOptimized(mydiv).top
ready
loopedOffsetOptimized2
loopedOffsetOptimized2(mydiv).top
ready
loopedOffsetOptimized3
loopedOffsetOptimized3(mydiv).top
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.