javascript Webkit 和 jQuery 可拖动跳转
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3523747/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Webkit and jQuery draggable jumping
提问by gobbledygook88
As an experiment, I created a few div's and rotated them using CSS3.
作为一个实验,我创建了一些 div 并使用 CSS3 旋转它们。
.items {
position: absolute;
cursor: pointer;
background: #FFC400;
-moz-box-shadow: 0px 0px 2px #E39900;
-webkit-box-shadow: 1px 1px 2px #E39900;
box-shadow: 0px 0px 2px #E39900;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
}
I then randomly styled them and made them draggable via jQuery.
然后我随机设置它们的样式并通过 jQuery 使它们可拖动。
$('.items').each(function() {
$(this).css({
top: (80 * Math.random()) + '%',
left: (80 * Math.random()) + '%',
width: (100 + 200 * Math.random()) + 'px',
height: (10 + 10 * Math.random()) + 'px',
'-moz-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-o-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
'-webkit-transform': 'rotate(' + (180 * Math.random()) + 'deg)',
});
});
$('.items').draggable();
The dragging works, but I am noticing a sudden jump while dragging the div's only in webkit browsers, while everything is fine in Firefox.
拖动有效,但我注意到仅在 webkit 浏览器中拖动 div 时突然跳转,而在 Firefox 中一切正常。
If I remove the position: absolutestyle, the 'jumping' is even worse. I thought there was maybe a difference in the transform origin between webkit and gecko, but they are both at the centre of the element by default.
如果我删除position: absolute样式,“跳跃”就更糟了。我认为 webkit 和 gecko 之间的转换原点可能有所不同,但默认情况下它们都位于元素的中心。
I have searched around already, but only came up with results about scrollbars or sortable lists.
我已经四处搜索了,但只得到了关于滚动条或可排序列表的结果。
Here is a working demo of my problem. Try to view it in both Safari/Chrome and Firefox. http://jsbin.com/ucehu/
这是我的问题的工作演示。尝试在 Safari/Chrome 和 Firefox 中查看它。http://jsbin.com/ucehu/
Is this a bug within webkit or how the browsers render webkit?
这是 webkit 中的错误还是浏览器如何呈现 webkit?
采纳答案by David Wick
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.
这是draggable依赖jqueryoffset()函数,offset()使用原生js函数的结果getBoundingClientRect()。最终,这是 jquery 核心无法补偿与getBoundingClientRect(). Firefox 的版本会getBoundingClientRect()忽略 css3 转换(旋转),而 chrome/safari (webkit) 不会。
hereis an illustration of the issue.
这是该问题的说明。
A hacky workaround:
一个hacky的解决方法:
replace following in jquery.ui.draggable.js
替换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.
最后一个monkeypatched您的版本jsbin。
回答by Liao San Kai
I draw a image to indicate the offset after rotate on different browsers as @David Wick's answer.
作为@David Wick 的回答,我绘制了一张图像来指示在不同浏览器上旋转后的偏移量。


Here's the code to fix if you don't want patch or modify jquery.ui.draggable.js
如果您不想修补或修改 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
或者你可以看演示
回答by ecmanaut
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:
大卫威克关于上面的总体方向是正确的,但计算正确的坐标比这更复杂。这是一个更准确的猴子补丁,基于 MIT 许可的 Firebug 代码,它应该适用于您拥有复杂 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):
使用较少的hacky(确保获得整个内容;您需要滚动):
//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.
遗憾的是 DOM 没有在本地公开这些计算。
回答by Dutch polyglot programmer
@ecmanaut: Great solution. Thanks for your efforts. To assist others I turned your solution into a monkey-patch. Copy below code to a file. Include the file after loading jquery-ui.js as follows:
@ecmanaut:很好的解决方案。感谢您的努力。为了帮助其他人,我将您的解决方案变成了猴子补丁。将以下代码复制到文件中。加载 jquery-ui.js 后包含文件如下:
<script src="javascripts/jquery/jquery.js"></script>
<script src="javascripts/jquery/jquery-ui.js"></script>
<!-- the file containing the monkey-patch to draggable -->
<script src="javascripts/jquery/patch_draggable.js"></script>
Here's the code to copy/paste into patch_draggable.js:
这是复制/粘贴到 patch_draggable.js 的代码:
function monkeyPatch_mouseStart() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.draggable.prototype._mouseStart ;
$.ui.draggable.prototype._mouseStart = function(event) {
var o = this.options;
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;
}
}
}
//Create and append the visible helper
this.helper = this._createHelper(event);
//Cache the helper size
this._cacheHelperProportions();
//If ddmanager is used for droppables, set the global draggable
if($.ui.ddmanager)
$.ui.ddmanager.current = this;
/*
* - Position generation -
* This block generates everything position related - it's the core of draggables.
*/
//Cache the margins of the original element
this._cacheMargins();
//Store the helper's css position
this.cssPosition = this.helper.css("position");
this.scrollParent = this.helper.scrollParent();
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = getViewOffset(this.element[0]);
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
$.extend(this.offset, {
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
});
//Generate the original position
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
//Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
//Set a containment if given in the options
if(o.containment)
this._setContainment();
//Trigger event + callbacks
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
//Recache the helper size
this._cacheHelperProportions();
//Prepare the droppable offsets
if ($.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
this.helper.addClass("ui-draggable-dragging");
//JWL: Hier vindt de jump plaats
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
return true;
};
}
monkeyPatch_mouseStart();
回答by UnLoCo
I prefer this workaround as it preserves the original handler
It removes the transform then restores it
我更喜欢这种解决方法,因为它保留了原始处理程序
它删除了转换然后恢复它
$(document).ready(function(){
// backup original handler
var _mouseStart = $.ui.draggable.prototype._mouseStart;
$.ui.draggable.prototype._mouseStart = function(event) {
//remove the transform
var transform = this.element.css('transform');
this.element.css('transform', 'none');
// call original handler
var result = _mouseStart.call(this, event);
//restore the transform
this.element.css('transform', transform);
return result;
};
});
demo(started from @Liao San-Kai jsbin)
演示(从@Liao San-Kai jsbin 开始)
回答by H-net
the answer of David Wick was very helpful... thanks... here i coded the same workaround for the resizeable, because it has the same problem:
大卫威克的回答非常有帮助...谢谢...在这里我为可调整大小编写了相同的解决方法,因为它有同样的问题:
search for the following in jquery.ui.resizable.js
在jquery.ui.resizable.js 中搜索以下内容
var o = this.options, iniPos = this.element.position(), el = this.element;
and replace with:
并替换为:
var o = this.options, iniPos = {top:this.element[0].offsetTop,left:this.element[0].offsetLeft}, el = this.element;
回答by Dennis Müller
I used a lot of the solutions to get dragging working correctly. BUT, it still reacted wrong to a dropzone (like it wasn't rotated). The Solution really is to use a parent container that is positioned relative.
我使用了很多解决方案来使拖动正常工作。但是,它仍然对 dropzone 做出了错误的反应(就像它没有旋转)。解决方案实际上是使用相对定位的父容器。
This saved me soooo much time.
这为我节省了太多时间。
<div id="drawarea">
<div class="rect-container h">
<div class="rect"></div>
</div>
</div>
.rect-container {
position:relative;
}
Full Solution here (it's not from me): http://jsfiddle.net/Sp6qa/2/
完整解决方案在这里(不是我的):http: //jsfiddle.net/Sp6qa/2/
Also I researched a lot. And its just like this, jQuery doesn't have any plans to change that current behavior in the future. All submitted tickets about that topic were closed. So just start out with having parentcontainers that are positioned relative. It works like a charm and should be futureproof.
我也研究了很多。就像这样,jQuery 没有任何计划在未来改变当前的行为。所有提交的关于该主题的票都已关闭。因此,只需从具有相对定位的父容器开始。它就像一种魅力,应该是面向未来的。
回答by achim
You have to set the parent container of the draggable element to "position: relative".
您必须将可拖动元素的父容器设置为“位置:相对”。

