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.
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