web-dev-qa-db-ja.com

WebkitとjQueryのドラッグ可能なジャンプ

実験として、いくつかの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;
    }

次に、それらをランダムにスタイル設定し、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();

ドラッグは機能しますが、Firefoxではすべてが正常であるのに、Webkitブラウザーでのみdivをドラッグしているときに突然ジャンプすることに気づきました。

position:absoluteスタイルを削除すると、「ジャンプ」はさらに悪化します。 webkitとgeckoの間で変換Originに違いがあるのではないかと思いましたが、デフォルトでは両方とも要素の中心にあります。

私はすでに周りを検索しましたが、スクロールバーまたはソート可能なリストに関する結果しか思いつきませんでした。

これが私の問題の実用的なデモです。 Safari/ChromeとFirefoxの両方で表示してみてください。 http://jsbin.com/ucehu/

これはWebkit内のバグですか、それともブラウザーがWebkitをレンダリングする方法ですか?

36
gobbledygook88

これは、ドラッグ可能なオブジェクトがjquery offset()関数に依存していることとoffset()がネイティブjs関数getBoundingClientRect()を使用していることの結果です。最終的に、これはgetBoundingClientRect()に関連する不整合を補正しないjqueryコアの問題です。 FirefoxのバージョンのgetBoundingClientRect()はcss3変換(回転)を無視しますが、chrome/safari(webkit)は無視しません。

ここ は問題の実例です。

ハッキーな回避策:

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 };

そして最後にあなたの jsbin のmonkeypatchedバージョン。

34
David Wick

@David Wickの答えとして、さまざまなブラウザで回転した後のオフセットを示す画像を描画します。

offset after rotate

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

またはあなたは デモ を見ることができます

32
Liao San Kai

David Wickは上記の一般的な方向性について正しいですが、正しい座標の計算はそれよりもはるかに複雑です。 MITライセンスされたFirebugコードに基づく、より正確なモンキーパッチは次のとおりです。これは、複雑なDOMがあるはるかに多くの状況で機能するはずです。

代わりに、以下を置き換えます。

    //ページ上の要素の絶対位置からマージンを引いたもの
 this.offset = this.positionAbs = this.element.offset();

ハッキーが少ない(すべてを取得するようにしてください。スクロールする必要があります):

    //ページ上の要素の絶対位置からマージンを引いたもの
 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; 
} 
} 
}

DOMがこれらの計算をネイティブに公開しないのは残念です。

21
ecmanaut

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

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

元のハンドラーが保持されるため、この回避策をお勧めします
変換を削除してから復元します

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

デモ (@ Liao San-Kai jsbinから開始)

6
UnLoCo

david Wickの答えは非常に役に立ちました...ありがとう...ここでは、同じ問題があるため、サイズ変更可能なものに対して同じ回避策をコーディングしました。

jquery.ui.resizable.js で以下を検索します

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

と置き換えます:

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

ドラッグを正しく機能させるために、多くのソリューションを使用しました。しかし、それでもドロップゾーンに対して間違った反応をしました(回転しなかったように)。解決策は、実際には、相対的に配置された親コンテナを使用することです。

これは私にとても多くの時間を節約しました。

<div id="drawarea">
    <div class="rect-container h">
        <div class="rect"></div>
    </div>
</div> 



.rect-container {
    position:relative; 
}

ここでの完全な解決策(私からではありません): http://jsfiddle.net/Sp6qa/2/

また、私はたくさん研究しました。そして、まさにこのように、jQueryには現在の動作を将来変更する予定はありません。そのトピックに関して提出されたすべてのチケットはクローズされました。したがって、相対的な位置にある親コンテナを用意することから始めます。それは魅力のように機能し、将来性があるはずです。

2
Dennis Müller

ドラッグ可能な要素の親コンテナを「position:relative」に設定する必要があります。

1
achim