Webkit and jQuery draggable jumping

David Wick is right about the general direction above, but computing the right coordinates is way more involved than that. Here's a more accurate monkey patch, based on MIT licensed Firebug code, which should work in far more situations where you have a complex DOM:

Instead replace:

    //The element's absolute position on the page minus margins
    this.offset = this.positionAbs = this.element.offset();

with the less hacky (be sure to get the whole thing; you'll need to scroll):

    //The element's absolute position on the page minus margins
    this.offset = this.positionAbs = getViewOffset(this.element[0]);

    function getViewOffset(node) {
      var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
      if (node) addOffset(node);
      return { left: x, top: y };

      function getStyle(node) {
        return node.currentStyle || // IE
               win.getComputedStyle(node, '');
      }

      function addOffset(node) {
        var p = node.offsetParent, style, X, Y;
        x += parseInt(node.offsetLeft, 10) || 0;
        y += parseInt(node.offsetTop, 10) || 0;

        if (p) {
          x -= parseInt(p.scrollLeft, 10) || 0;
          y -= parseInt(p.scrollTop, 10) || 0;

          if (p.nodeType == 1) {
            var parentStyle = getStyle(p)
              , localName   = p.localName
              , parent      = node.parentNode;
            if (parentStyle.position != 'static') {
              x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
              y += parseInt(parentStyle.borderTopWidth, 10) || 0;

              if (localName == 'TABLE') {
                x += parseInt(parentStyle.paddingLeft, 10) || 0;
                y += parseInt(parentStyle.paddingTop, 10) || 0;
              }
              else if (localName == 'BODY') {
                style = getStyle(node);
                x += parseInt(style.marginLeft, 10) || 0;
                y += parseInt(style.marginTop, 10) || 0;
              }
            }
            else if (localName == 'BODY') {
              x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
              y += parseInt(parentStyle.borderTopWidth, 10) || 0;
            }

            while (p != parent) {
              x -= parseInt(parent.scrollLeft, 10) || 0;
              y -= parseInt(parent.scrollTop, 10) || 0;
              parent = parent.parentNode;
            }
            addOffset(p);
          }
        }
        else {
          if (node.localName == 'BODY') {
            style = getStyle(node);
            x += parseInt(style.borderLeftWidth, 10) || 0;
            y += parseInt(style.borderTopWidth, 10) || 0;

            var htmlStyle = getStyle(node.parentNode);
            x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
            y -= parseInt(htmlStyle.paddingTop, 10) || 0;
          }

          if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
          if ((Y = node.scrollTop))  y += parseInt(Y, 10) || 0;
        }
      }
    }

It's a shame the DOM doesn't expose these calculations natively.


This is a result of draggable's reliance on the jquery offset() function and offset()'s use of the native js function getBoundingClientRect(). Ultimately this is an issue with the jquery core not compensating for the inconsistencies associated with getBoundingClientRect(). Firefox's version of getBoundingClientRect() ignores the css3 transforms (rotation) whereas chrome/safari (webkit) don't.

here is an illustration of the issue.

A hacky workaround:

replace following in jquery.ui.draggable.js


//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();

with


//The element's absolute position on the page minus margins
this.offset = this.positionAbs = { top: this.element[0].offsetTop, 
                                   left: this.element[0].offsetLeft };

and finally a monkeypatched version of your jsbin.


I draw a image to indicate the offset after rotate on different browsers as @David Wick's answer.

offset after rotate

Here's the code to fix if you don't want patch or modify jquery.ui.draggable.js

$(document).ready(function () {
    var recoupLeft, recoupTop;
    $('#box').draggable({
        start: function (event, ui) {
            var left = parseInt($(this).css('left'),10);
            left = isNaN(left) ? 0 : left;
            var top = parseInt($(this).css('top'),10);
            top = isNaN(top) ? 0 : top;
            recoupLeft = left - ui.position.left;
            recoupTop = top - ui.position.top;
        },
        drag: function (event, ui) {
            ui.position.left += recoupLeft;
            ui.position.top += recoupTop;
        }
    });
});

or you can see the demo