Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery draggable shows helper in wrong place after page scrolled

I'm using jQuery draggable and droppable for a work-planning system I'm developing. Users drag jobs to a different day or user, and then data is updated using an ajax call.

Everything works fine, except when I scroll down the main page (Jobs appear on a large week planner that exceeds the bottom of my browser window). If I try and drag a draggable element here, the element appears above my mouse cursor the same amount of pixels as I've scrolled down.. The hover state still works fine and the functionality is bang on but it doesn't look right.

I'm using jQuery 1.6.0 and jQuery UI 1.8.12.

I'm sure there's a offset function I need to add but I don't know where to apply it, or if there's a better way. Here's my .draggable() initialisation code:

$('.job').draggable({
  zIndex: 20,
  revert: 'invalid',
  helper: 'original',
  distance: 30,
  refreshPositions: true,
});

Any idea what I can do to fix this?

like image 503
Alex Avatar asked Apr 26 '11 14:04

Alex


People also ask

How do you get the position of the draggable element?

The . position() method allows us to retrieve the current position of an element relative to the offset parent. Contrast this with . offset() , which retrieves the current position relative to the document.

Why is jQuery draggable not working?

You have one of these problems: Your jQuery or jQuery UI Javascript path files are wrong. Your jQuery UI does not include draggable. Your jQuery or jQuery UI Javascript files are corrupted.

How do I limit a draggable area?

Limit draggable area using 'containment' option It is simple. All you have to do is, add an option called containment to the draggable() method. The containment option has a value parent.


15 Answers

This might be a related bug report, it's been around for quite a while: http://bugs.jqueryui.com/ticket/3740

It seems to happen on every browser I tested (Chrome, FF4, IE9). There are a few ways you can work around this issue:

1. Use position:absolute; in your css. Absolutely positioned elements don't seem to be affected.

2. Make sure the parent element (event if it's the body) has overflow:auto; set. My test showed that this solution fixes the position, but it disables the autoscroll functionality. You can still scroll using the mousewheel or the arrow keys.

3. Apply the fix suggested in the above bug report manually and test thouroughly if it causes other problems.

4. Wait for an official fix. It's scheduled to jQuery UI 1.9, although it has been postponed a few times in the past.

5. If you're confident that it happens on every browser, you can put these hacks into the affected draggables' events to correct the calculations. It's a lot of different browsers to test though, so it should only be used as a last resort:

$('.drag').draggable({
   scroll:true,
   start: function(){
      $(this).data("startingScrollTop",$(this).parent().scrollTop());
   },
   drag: function(event,ui){
      var st = parseInt($(this).data("startingScrollTop"));
      ui.position.top -= $(this).parent().scrollTop() - st;
   }
});
like image 67
DarthJDG Avatar answered Oct 01 '22 01:10

DarthJDG


This solution works without adjusting the positioning of anything, you just clone the element and make it absolutely positioned.

$(".sidebar_container").sortable({
  ..
  helper: function(event, ui){
    var $clone =  $(ui).clone();
    $clone .css('position','absolute');
    return $clone.get(0);
  },
  ...
});

The helper can be a function which needs to return the DOM element to drag with.

like image 44
mordy Avatar answered Oct 03 '22 01:10

mordy


This worked for me:

start: function (event, ui) {
   $(this).data("startingScrollTop",window.pageYOffset);
},
drag: function(event,ui){
   var st = parseInt($(this).data("startingScrollTop"));
   ui.position.top -= st;
},
like image 42
Ashraf Fayad Avatar answered Sep 30 '22 01:09

Ashraf Fayad


Sorry for writing another answer. As none of the solutions in the above answer could be used by me I did a lot of Googling and made many frustrating minor edits before finding another reasonable solution.

This issue seems to occur whenever any of the parent elements have position set to 'relative'. I had to reshuffle my markup and alter my CSS but by removing this property from all parents, I was able to get .sortable() working properly in all browsers.

like image 27
Joshua Bambrick Avatar answered Oct 02 '22 01:10

Joshua Bambrick


It looks like this bug comes around very often, and every time there is a different solution. None of the above, or anything else I found on the internet worked. I'm using jQuery 1.9.1 and Jquery UI 1.10.3. This is how I fixed it:

$(".dragme").draggable({
  appendTo: "body",
  helper: "clone",
  scroll: false,
  cursorAt: {left: 5, top: 5},
  start: function(event, ui) {
    if(! $.browser.chrome) ui.position.top -= $(window).scrollTop();
  },
  drag: function(event, ui) {
    if(! $.browser.chrome) ui.position.top -= $(window).scrollTop();
  }
});

Works in FF, IE, Chrome, I've not yet tested it in other browsers.

like image 23
kazmer Avatar answered Sep 30 '22 01:09

kazmer


I delete the overflow:

html {overflow-y: scroll; background: #fff;}

And it works perfectly!

like image 24
Miguel Puig Avatar answered Oct 01 '22 01:10

Miguel Puig


This bug got moved to http://bugs.jqueryui.com/ticket/6817 and, as of about 5 days ago (Dec 16, 2013 or thereabout) appears to have finally been fixed. The suggestion right now is to use the latest development build from http://code.jquery.com/ui/jquery-ui-git.js or wait for version 1.10.4 which should contain this fix.

Edit: It seems this fix might now be part of http://bugs.jqueryui.com/ticket/9315 which isn't scheduled to drop until version 1.11. Using the above linked source control version of jQuery does seem to fix the issue for me and @Scott Alexander (comment below).

like image 35
Patrick Avatar answered Oct 02 '22 01:10

Patrick


Seems remarkable that this bug should have gone unfixed so long. For me it's a problem on Safari and Chrome (i.e. the webkit browsers) but not on IE7/8/9 nor on Firefox which all work fine.

I found that setting absolute or fixed, with or without !important, didn't help so in the end I added a line to my drag handler function:

ui.position.top += $( 'body' ).scrollTop();

I was expecting to need to make that line webkit-specific but curiously it worked fine everywhere. (Expect a comment from me soon saying 'er no, actually it messed up all the other browsers'.)

like image 36
Allen Avatar answered Oct 01 '22 01:10

Allen


what i have done is:

$("#btnPageBreak").draggable(
        {
          appendTo: 'body',
          helper: function(event) {
            return '<div id="pageBreakHelper"><img  id="page-break-img"  src="page_break_cursor_red.png" /></div>';
          },
          start: function(event, ui) {
          },
          stop: function(event, ui) {
          },
          drag: function(event,ui){
            ui.helper.offset(ui.position);
         }
        });
like image 29
Pankaj Sharma Avatar answered Sep 29 '22 01:09

Pankaj Sharma


I'm not fully sure that this will work in every case but this workaround I just did work for me on all the various situations I had to test (and where the previous proposed solutions given here and elsewhere all failed)

(function($){
$.ui.draggable.prototype._getRelativeOffset = function()
{
    if(this.cssPosition == "relative") {
        var p = this.element.position();
        return {
            top: p.top - (parseInt(this.helper.css("top"),10) || 0)/* + this.scrollParent.scrollTop()*/,
            left: p.left - (parseInt(this.helper.css("left"),10) || 0)/* + this.scrollParent.scrollLeft()*/
        };
    } else {
        return { top: 0, left: 0 };
    }
};
}(jQuery));

( I submitted it to the jQuery.ui tracker to see what they think about it.. would be cool if that 4year old bug could be finally corrected :/ )

like image 20
vor Avatar answered Sep 30 '22 01:09

vor


I am using JQuery draggable on Firefox 21.0, and I am having the same problem. The cursor stays an inch above the helper image. I think this is an issue in the Jquery itself which has still not been resolved. I found a workaround to this problem, which is using the "cursorAt" property of the draggable. I used it as follows, but one can change this according to its requirement.

$('.dragme').draggable({
helper: 'clone',    
cursor: 'move',    
cursorAt: {left: 50, top: 80}
});

Note: This will be applicable for all browsers, so after using this code check your page in all browsers to get the correct left and top positions.

like image 40
impiyush Avatar answered Sep 29 '22 01:09

impiyush


This is what worked for me, after adapting everything I have read on the problem.

$(this).draggable({
  ...
  start: function (event, ui) {
    $(this).data("scrollposTop", $('#elementThatScrolls').scrollTop();
    $(this).data("scrollposLeft", $('#elementThatScrolls').scrollLeft();
  },
  drag: function (event, ui) {
    ui.position.top += $('#elementThatScrolls').scrollTop();
    ui.position.left += $('#elementThatScrolls').scrollLeft();
  },
  ...
}); 

*elementThatScrolls is the parent or ancestor with overflow:auto;

Works out the position of the scrolling element and adds to the position of the helper each time it is moved/dragged.

Hope that helps someone, as I wasted major time on this.

like image 41
Rnubi Avatar answered Oct 02 '22 01:10

Rnubi


This seems to do the workaround without a need to use the position:absolute in the parent :)

$("body").draggable({
     drag: function(event, ui) { 
         return false; 
     } 
});
like image 42
TomCe Avatar answered Oct 03 '22 01:10

TomCe


I managed to fix the same issue with adding display: inline-block to the dragged items

Adding position: relative did NOT work for me (jQuery overrides it with position: absolute on drag start)

Used jQuery versions:

  • jQuery - 1.7.2
  • jQuery UI - 1.11.4
like image 20
Plamen Avatar answered Oct 03 '22 01:10

Plamen


I had the same problem and found that all this was because of a bootstrap navbar "navbar-fixed-top" class with position to fixed, that move down my <body> 60px lower than <html> top. So when I was moving draggable forms in my site, the cursor appeared 60px on top of form.

You can see that in Chrome debugging tool, in the Elements interface, click on the <body> tag and you will see a gap between top and the body.

Since I'm using this navbar all over my applications and didn't want to modify my initializing call to drag (as per DarthJDG's procedure) for each forms. I decided to extend draggable widget this way and simply add a yOffset in my draggable initialisation.

(function($) {
  $.widget("my-ui.draggable", $.ui.draggable, {
    _mouseDrag: function(event, noPropagation) {

      //Compute the helpers position
      this.position = this._generatePosition(event);
      this.positionAbs = this._convertPositionTo("absolute");

      //Call plugins and callbacks and use the resulting position if something is returned
      if (!noPropagation) {
        var ui = this._uiHash();
        if(this._trigger('drag', event, ui) === false) {
          this._mouseUp({});
          return false;
        }
        this.position = ui.position;
      }
      // Modification here we add yoffset in options and calculation
      var yOffset = ("yOffset" in this.options) ? this.options.yOffset : 0;

      if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
      if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top-yOffset+'px';
      if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);

      return false;
    }
  });
})(jQuery);

Now when I initialize the draggable element/form in a site using bootstrap navbar:

// Make the edit forms draggable
$("#org_account_pop").draggable({handle:" .drag_handle", yOffset: 60});

Of course, you will have to experiment to determine the right yOffset as per your own site.

Thanks anyway to DarthJDG who pointed me the right direction. Cheers!

like image 43
fled Avatar answered Oct 03 '22 01:10

fled