Webkit和jQuery可拖动跳跃

作为一个实验,我创build了几个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; } 

然后我随机devise了它们,并通过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(); 

拖动的作品,但我注意到,只有在webkit浏览器中拖动div时突然跳跃,而在Firefox中的一切都很好。

如果我删除了绝对风格的位置 ,那么“跳跃”就更糟了。 我认为webkit和gecko之间的转换原点可能有所不同,但是它们都默认处于元素的中心。

我已经search过,但只有滚动条或sorting列表的结果。

这是我的问题的一个工作演示。 尝试在Safari / Chrome和Firefox中查看它。 http://jsbin.com/ucehu/

这是一个webkit中的错误,或浏览器如何呈现webkit?

这是可拖动的依赖于jquery offset()函数和offset()对本地js函数getBoundingClientRect() 。 最终这是jquery核心的一个问题,不能补偿与getBoundingClientRect()相关的不一致。 Firefox的getBoundingClientRect()版本会忽略css3变换(旋转),而chrome / safari(webkit)则不会。

这里是一个问题的例子。

一个hacky的解决方法:

replacejquery.ui.draggable.js中的以下内容

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

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

最后是你的jsbin的monkeypatched版本。

我在@David Wick的答案中绘制图像以指示在不同浏览器上旋转后的偏移量。

旋转后偏移

如果您不想修补或修改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; } }); }); 

或者你可以看到演示

David Wick对于上面的总体方向是正确的,但计算正确的坐标是比这更多的参与。 这是一个更准确的猴子补丁,基于麻省理工学院许可Firebug代码,这应该在更多的情况下,你有一个复杂的DOM:

取而代之:

  //元素在页面上的绝对位置减去边距
     this.offset = this.positionAbs = this.element.offset(); 

与lesshacky(一定要得到整个事情,你需要滚动):

  //元素在页面上的绝对位置减去边距
     this.offset = this.positionAbs = getViewOffset(this.element [0]);

    函数getViewOffset(节点){
       var x = 0,y = 0,win = node.ownerDocument.defaultView || 窗口;
       if(node)addOffset(node);
      返回{left:x,top:y};

      函数getStyle(node){
        返回node.currentStyle ||  // IE
                win.getComputedStyle(node,'');
       }

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

        如果(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;
           }

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

这是一个遗憾的是,DOM本身不公开这些计算。

@ecmanaut:伟大的解决scheme。 感谢您的努力。 为了帮助他人,我把你的解决scheme变成了一个猴子补丁。 将以下代码复制到文件中。 加载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> 

以下是复制/粘贴到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(); 

大卫威克的答案是非常有益的…谢谢…在这里我编码的resize相同的解决方法,因为它有同样的问题:

在jquery.ui.resizable.js中search以下内容

 var o = this.options, iniPos = this.element.position(), el = this.element; 

并replace为:

 var o = this.options, iniPos = {top:this.element[0].offsetTop,left:this.element[0].offsetLeft}, el = this.element; 

我使用了很多解决scheme来正确拖动工作。 但是,它仍然反应到一个dropzone(像它不旋转)。 解决scheme实际上是使用相对定位的父容器。

这节省了我很多时间。

 <div id="drawarea"> <div class="rect-container h"> <div class="rect"></div> </div> </div> .rect-container { position:relative; } 

完整的解决scheme(这不是从我): http : //jsfiddle.net/Sp6qa/2/

我也研究了很多。 就像这样,jQuery没有任何计划在未来改变当前的行为。 所有提交的有关该主题的门票都closures了。 所以刚开始有相对定位的父容器。 它像一个魅力,应该是未来的保障。

我更喜欢这个解决方法,因为它保留了原始处理程序
它删除转换然后恢复它

 $(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; }; }); 

演示 (从@廖三启jsbin开始)

您必须将可拖动元素的父容器设置为“position:relative”。