39 votes

Webkit et jQuery : saut à la volée

À titre expérimental, j'ai créé quelques div's et je les ai fait pivoter à l'aide de 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;
    }

Je les ai ensuite stylisés de manière aléatoire et je les ai rendus déplaçables via 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();

Le glissement fonctionne, mais je remarque un saut soudain lors du glissement des div's uniquement dans les navigateurs webkit, alors que tout va bien dans Firefox.

Si je retire le position : absolue Le style, le "saut" est encore pire. Je pensais qu'il y avait peut-être une différence dans l'origine de la transformation entre webkit et gecko, mais ils sont tous les deux au centre de l'élément par défaut.

J'ai déjà fait des recherches, mais je n'ai trouvé que des résultats concernant les barres de défilement ou les listes triables.

Voici une démonstration de mon problème. Essayez de la visualiser à la fois dans Safari/Chrome et dans Firefox. http://jsbin.com/ucehu/

S'agit-il d'un bogue dans webkit ou dans la façon dont les navigateurs rendent webkit ?

0 votes

Je vois la même chose (et aussi dans Opera). Avez-vous trouvé une solution ?

36voto

Liao San-Kai Points 138

Je dessine une image pour indiquer le décalage après rotation sur différents navigateurs comme dans la réponse de @David Wick.

offset after rotate

Voici le code à corriger si vous ne voulez pas patcher ou modifier 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;
        }
    });
});

ou vous pouvez voir le démo

0 votes

Il fonctionne comme un charme. Il manque juste une parenthèse après la fonction drag :. Merci de votre compréhension.

0 votes

Merci, cela m'a sauvé la mise.

0 votes

Merci, vous avez résolu l'un de mes problèmes les plus irritants !

34voto

David Wick Points 4997

C'est le résultat de la dépendance de draggable à l'égard de l'outil jquery offset() et offset() L'utilisation de la fonction native js getBoundingClientRect() . En fin de compte, il s'agit d'un problème lié au fait que le noyau de jquery ne compense pas les incohérences liées à l'utilisation de la technologie getBoundingClientRect() . La version de Firefox de getBoundingClientRect() ignore les transformations css3 (rotation) alors que chrome/safari (webkit) ne le font pas.

aquí est une illustration de ce problème.

Une solution de contournement peu pratique :

Remplacer le texte suivant par le texte suivant jquery.ui.draggable.js

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

avec

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

et enfin une version monkeypatchée de votre jsbin .

1 votes

Et il existe déjà une question ouverte à ce sujet : bugs.jquery.com/ticket/8362

0 votes

Cela n'a pas fonctionné pour moi (ni la correction plus longue ci-dessous). Mais la solution consiste à faire pivoter l'objet interne et à faire glisser son enveloppe parentale. De cette façon, la position n'est pas du tout liée à la rotation

21voto

ecmanaut Points 1688

David Wick a raison en ce qui concerne l'orientation générale ci-dessus, mais le calcul des bonnes coordonnées est beaucoup plus complexe que cela. Voici un patch plus précis, basé sur le code Firebug sous licence MIT, qui devrait fonctionner dans beaucoup plus de situations où vous avez un DOM complexe :

Remplacez plutôt :

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

avec le moins hacky (assurez-vous de lire l'intégralité du texte ; vous devrez faire défiler l'écran) :

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

Il est dommage que le DOM n'expose pas ces calculs de manière native.

0 votes

Je sais que c'est une vieille question mais j'espère avoir de l'aide. J'ai eu un problème similaire et j'ai utilisé à la fois votre code et celui que David Wick a soumis. Ils fonctionnent comme un charme la première fois quand je fais glisser l'objet, mais le saut continue après que je le dépose et que je le fasse glisser à nouveau. @ecmanaut savez-vous comment résoudre ce problème ?

11voto

@ecmanaut : Excellente solution. Merci pour vos efforts. Pour aider les autres, j'ai transformé votre solution en un monkey-patch. Copiez le code ci-dessous dans un fichier. Incluez le fichier après le chargement de jquery-ui.js comme suit :

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

Voici le code à copier/coller dans 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();

3 votes

J'ai reçu une erreur : Uncaught TypeError : Cannot read property 'offset' of undefined jquery-ui.js:1298

0 votes

Même chose pour moi - je me demande si quelqu'un a mis au point une mise à jour de ce qui précède. :'-(

5voto

H-net Points 51

La réponse de David Wick a été très utile... merci... J'ai codé la même solution pour le redimensionnable, car il a le même problème :

rechercher les éléments suivants dans jquery.ui.resizable.js

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

et le remplacer par :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X