80

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?

Alex
  • 8,353
  • 9
  • 45
  • 56

21 Answers21

109

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;
   }
});
DarthJDG
  • 16,511
  • 11
  • 49
  • 56
  • 12
    That's great, thank you! (Settings overflow:auto fixed it immediately. I applied the style to the individual table cells. – Alex Apr 28 '11 at 08:23
  • 1
    I discovered the bug also happens if your source draggable object is a child of a position: fixed element. The only thing that worked for me was to move the clone out of the fixed element -- start: function(event,ui) { ui.helper.appendTo($('body')); } -- and apply the patch in the bug report. hth – Altreus May 18 '11 at 16:40
  • 2
    http://jsfiddle.net/a2hzt/5/ overflow-y: scroll on the html element also creates this problem on firefox (chrome works), just scroll down and try dragging. – digitalPBK Jun 01 '12 at 02:46
  • Answer by @Josh 'Bambi' Bambrick also seems to do the trick. So removing `position: relative;` from all the parents is also good solution. – wojtiku Apr 17 '13 at 11:34
  • The last resort option ALMOST worked for me, except that the divs I was scrolling weren't in the parent layer (they were several up), so I defined them by the selector. Also, for some reason your code has -= in the ui.position setter, rather than +=, which is what fixed my situation. In case anyone else gets stuck on this, try (in the drag function): `drag: function(event,ui){ var st = parseInt($(this).data("startingScrollTop")); ui.position.top += $("#parent-div-selector").scrollTop() - st; }` – Calciphus Jun 13 '13 at 01:17
  • this is driving me insane. I got horizontal scrolling (simple slider with draggable elements inside the slides) and their positioning is crazy. No solution worked so far :-/ – ProblemsOfSumit Jul 19 '13 at 16:54
  • ok i figured out my problem: the solution was to delete the "containment" option! I searched for hours! – ProblemsOfSumit Jul 19 '13 at 17:03
  • 8
    It's ba-a-ack, for `helper: "clone"`. http://bugs.jqueryui.com/ticket/9315 Level **blocker**, a notch above major. tj.vantoll says "6 draggable fixes that landed in 1.10.3 might be suspect." Simplest example yet: http://jsfiddle.net/7AxXE/ – Bob Stein Jul 28 '13 at 20:43
  • 7
    Confirmed, this bug is in **jQuery UI 1.10.3** but not 1.10.2 – Bob Stein Jul 28 '13 at 21:09
  • For some reason, the overflow or position didn't work for me. But changing in the source code did. But rolling back to 1.10.2 felt like a more proper solution in our project. – He Nrik Oct 21 '13 at 09:47
  • 2
    Looks like the problem also exists in **jQuery UI 1.10.4**. I'll just stick to **1.10.2** for now! – lhan Mar 26 '14 at 19:41
  • `"1.11.0"` bug still here. – Roman Kiselenko Sep 21 '14 at 13:05
  • looks like the bug still exists in "1.11.2" as well – Victor Dec 03 '14 at 18:40
  • My problem that i have to work on long list table have scroll and draggable items in it . This bug still in "1.11.2". Any suggestion for this. I have tried all solution i founded but still can't fix it. Here is the [jsfiddle](http://jsfiddle.net/darkhunter_93/dvcq813r/5/) – thanh Dec 06 '14 at 10:25
  • For me the bug is **fixed when using 1.11.2** (I came from 1.10.4). – Lode Jan 16 '15 at 12:03
  • racked my brains on this exact issue and `overflow:auto;` totally worked for me, thanks man. – Cesar Bielich Apr 23 '15 at 06:21
  • For me, the bug was not here with **1.10.4** but it reappeared with version **1.11.2**. `overflow:auto` or `overflow:hidden` are good workarounds. I don't know how to reopen the bug for the jQueryUI team. – Fabien Quatravaux Apr 23 '15 at 14:30
  • 1
    Point 1) is quite misleading. Actually it's not about absolutely positioning elements, but being sure that their container have position (absolute or relative) specified. I fixed this problem setting position:relative on the container, because helper element was previously positioned absolutely to an outer container. – andrea.spot. Apr 28 '15 at 08:04
  • Thank you for this answer, I had everything but the overflow: auto and this solution fixed my errors – Shawn V Jun 25 '15 at 17:08
  • @DartJDG, may I ask you to take a look at a draggable related question here : https://stackoverflow.com/questions/54498364/jquery-drag-and-drop-simulation-does-not-work-for-the-last-draggable – Istiaque Ahmed Feb 02 '19 at 23:20
  • 1
    Wow! I've spent a whole day trying to find a workaround, but a simple overflow: auto; did the trick! The biggest thank to you ever :D – Konstantin Jul 22 '19 at 14:19
  • Hey guys, I'm here from 2022 to tell you this bug still exists in version 1.13.2. Adding `overflow:auto;` to the parent element fixes it, just note that this will cause a bug with sticky elements. – appel Oct 15 '22 at 02:30
46

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.

sdespont
  • 13,915
  • 9
  • 56
  • 97
mordy
  • 1,035
  • 9
  • 6
11

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;
},
lhan
  • 4,585
  • 11
  • 60
  • 105
Ashraf Fayad
  • 1,433
  • 4
  • 17
  • 31
  • fix my problem, `"1.11.0"` – Roman Kiselenko Sep 21 '14 at 13:19
  • That's an funny solution, but It will only work on browsers that do have the bug (Chrome). Browsers that don't have the bug will now have it but inverted :) – manu Jun 04 '15 at 09:18
  • Note: some JS-Parsers might complain about "missing radix" when using this solution. To fix, replace var st = parseInt($(this).data("startingScrollTop")); with var st = parseInt($(this).data("startingScrollTop"), 10); (i.e. provide the numeral system to be used). 10 is the default, but some parsers might require this to be present nontheless (e.g. webpack) – ripper17 Mar 06 '18 at 11:36
8

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.

Joshua Bambrick
  • 2,669
  • 5
  • 27
  • 35
  • The same for me here. Any `position: relative;` in any parent causes this to happen. – wojtiku Apr 17 '13 at 11:31
  • totally aggree! For me, it was just an inline style in the body with `position: relative;` which had to be removed.. parent elements between the draggable and the body seems not to make problemes here.. – con Sep 08 '13 at 14:28
  • 1
    omg that was it thanks, garrr why are people not using Dragula in this day and age - thanks! – Dominic Nov 10 '15 at 14:22
  • Thank you so much! Parent of parent having position:relative caused it for me – wjk2a1 Jan 26 '18 at 09:41
  • Chiming in to say this solved my issues! I'm using the js-parsons library, which has not been updated in years, so it still uses jQuery 1.7.2. Removing the `position: relative` from one of its parents fixed the issue. – tsumnia Sep 17 '21 at 17:31
7

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.

kazmer
  • 501
  • 4
  • 12
5

I delete the overflow:

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

And it works perfectly!

Miguel Puig
  • 141
  • 1
  • 4
  • 7
5

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

Patrick
  • 2,672
  • 3
  • 31
  • 47
4

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'.)

Allen
  • 76
  • 2
  • 1
    This works, but when you scroll while dragging, it still shifts the y position. The DarthJDG's solution number 5 works also when scrolling while dragging. – mirelon Mar 06 '13 at 11:09
3

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);
         }
        });
Pankaj Sharma
  • 1,833
  • 1
  • 17
  • 22
1

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 :/ )

vor
  • 91
  • 3
1

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.

impiyush
  • 754
  • 1
  • 8
  • 17
1

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.

Rnubi
  • 11
  • 2
1

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; 
     } 
});
TomCe
  • 9
  • 3
  • Thanks! this is the only thing that worked for me. (I'm not sure I understand the problem.. my problem is that dragging, dragging scrolling work fine when the drag starts with the scrollbar at the top. If the scroll bar starts lower then I get an offset issue) – John Smith Jul 11 '14 at 17:08
1

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
Plamen
  • 330
  • 2
  • 8
  • `position: relative` may have worked if you moved it to a higher-level container element. I know it doesn't help now, but just for reference and maybe for a future troubled developer. – gdibble Aug 22 '16 at 23:24
1

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!

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
fled
  • 161
  • 1
  • 5
  • 12
1

I'm using a sticky nav at the top and thought that was what was causing the scroll issue everyone else seems to be having. I tried everyone else's idea with cursorAt, I tried containment, and nothing worked EXCEPT unsetting my html overflow in the CSS! Insane how a little CSS will screw everything up. This is what I did and it works like a charm:

html {
  overflow-x: unset;
}

body {
  overflow-x: hidden;
}
Alex
  • 8,353
  • 9
  • 45
  • 56
Altus Nix
  • 51
  • 3
0

I quit using jQueryUi draggable and moved to use a better plugin called jquery.even.drag, much smaller code size, without all the crap jQueryUI has.

I've made a demo page using this plugin inside a container which has position:fixed

Demo

Hope this helps somebody, because this problem is BIG.

vsync
  • 118,978
  • 58
  • 307
  • 400
0

I had similar issue, only with particular IE 10 running on Windows 8. All other browsers worked fine. Removing cursorAt: { top: 0 } solved the problem.

nmm
  • 108
  • 7
0

I tried various answers in here, including updating jQuery and found that this worked for me. I tested this on latest chrome and IE. I guess this will be a problem with different solutions depending on your UI layout.

$('{selector}').draggable({
    start: function(event, ui) {
        ui.position.top -= $(document).scrollTop();
    },
    drag: function(event, ui) {
        ui.position.top -= $(document).scrollTop();
    }
});
Lee Willis
  • 1,552
  • 9
  • 14
0

Why not take the cursor position? i'am using the sortable plugin and i fix it with this code:

sort: function(e, ui) {
    $(".ui-sortable-handle.ui-sortable-helper").css({'top':e.pageY});
}
Aztrozero
  • 133
  • 1
  • 8
0

Use appendTo: "body' and set position with cursorAt. This works fine form me.

Example of my icon following the mouse cursor even after scrolling the page

$('.js-tire').draggable
      tolerance: 'fit',
      appendTo: 'body',
      cursor: 'move',
      cursorAt:
        top: 15,
        left: 18
      helper: (event) ->
        $('<div class="tire-icon"></div>').append($('<img src="/images/tyre_32_32.png" />'))
      start: (event, ui) ->
        if $(event.target).hasClass 'in-use'
          $('.js-send-to-maintenance-box').addClass 'is-highlighted'
        else
          $('.ui-droppable').addClass 'is-highlighted'
      stop: (event, ui) ->
        $('.ui-droppable')
          .removeClass 'is-highlighted'
          .removeClass 'is-dragover'