offset vs getBoundingClientRect() (v6)

Revision 6 of this benchmark created by Marcin 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="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.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.