56

I have a problem with a jQuery UI 1.7.2 sortable list in Firefox 3.6, IE7-8 work fine. When I'm scrolled down a bit, the helper element seems to have an offset of the same height that I'm scrolled down from the mouse pointer which makes it impossible to see which item you originally started dragging. How do I fix this or work around the issue? If there is no fix what is a really good alternative drag-able plugin?

Here are my initialization parameters for the sortable.

$("#sortable").sortable( {placeholder: 'ui-state-highlight'  } );
$("#sortable").disableSelection();
skaffman
  • 398,947
  • 96
  • 818
  • 769
James
  • 12,636
  • 12
  • 67
  • 104
  • 4
    Here's a gif that demonstrates the bug (referenced from the jQuery forum post in the accepted answer): http://i.imgur.com/phYGO.gif – mcw Apr 08 '14 at 19:57

15 Answers15

132

If you want to prevent browser sniffing, the CSS only solution is to set the ul or a container style to overflow: auto. If you look at the source through firebug, it's the way jQuery does it in their example.

munch
  • 6,311
  • 4
  • 23
  • 27
  • This is the only answer that worked for me using JQuery-UI draggables and droppables. – StuperUser Feb 14 '11 at 10:47
  • 1
    This worked perfectly for me on all browsers and is certainly the simplest/cleanest solution. Cheers! – deshg Jun 21 '11 at 12:16
  • 9
    definitely the cleanest solution. Not being able to define a container `position:relative` is a quite serious limitation – Bart Gloudemans Jul 27 '11 at 15:05
  • Thanks! As mentioned before, no position:relative is unacceptable... the overflow:auto is still causing me some trouble with elements positioned absolutely, but I'm sure it's easy to fix. – Robin Aug 09 '11 at 22:51
  • 3
    This worked for me while the `position:relative` didn't, thanks. – Björn Sep 06 '11 at 16:08
  • For me that one was also the only one that worked... thanks :-) – Michi Apr 27 '12 at 09:43
  • Great find, thanks. It's simply not realistic to avoid having `position: relative` on any parent element. – bbodien Sep 25 '12 at 10:12
  • 1
    Combinasion of `position: static;` and `overflow: auto;` did it for me – Pierluc SS Jan 29 '13 at 17:02
  • Thank you @munch. This worked on a fairly complex page. Per your suggestion, I applied overflow: auto to a div that contained 2 sortable lists. – Ken Palmer May 29 '13 at 18:53
  • best answer also for me, but please elaborate more! "if you want to prevent browser sniffing" looks unrelated to the question, explain better in order to help more, i tried this just because of positive feedback – danza Oct 08 '13 at 09:18
  • In my case I had a horizontal list, so `overflow-x: auto` and `overflow-y: hidden` did the trick! Thanks for this @munch! – MonkeyCoder Jan 18 '14 at 08:38
  • Combination of `position: relative` AND `overflow:auto` did it for me. The layout was quite complex, combining static & relative in the containers. Thank you buddy! – Stanimir Stoyanov Jan 27 '15 at 13:35
  • Can someone explain why is this solving the problem? – Ned Jul 28 '16 at 12:45
  • It works but unfortunately causing scrollbars. But at least it's the lesser of the two evils ;) – user2718671 Oct 27 '16 at 13:07
48

Using overflow: auto; was not an option for me. I was able to work around this issue with this sort event handler:

$(...).sortable({
    ...
    sort: function(event, ui) {
        var $target = $(event.target);
        if (!/html|body/i.test($target.offsetParent()[0].tagName)) {
            var top = event.pageY - $target.offsetParent().offset().top - (ui.helper.outerHeight(true) / 2);
            ui.helper.css({'top' : top + 'px'});
        }
    },
    ...
});

It is not perfect, but it doesn't require browser sniffing. Tested in IE8, Chrome and Firefox.

Edit: This is using jQuery 1.10.0 and jQuery UI 1.10.3.

rtcherry
  • 4,840
  • 22
  • 27
25

I was seeing this issue and was able to solve it by removing the css rule position:relative from one of the containing divs on my page. See also: http://forum.jquery.com/topic/sortable-offset-when-element-is-dragged-and-page-scrolled-down-ff

craig
  • 274
  • 3
  • 2
  • 1
    Worked for me, but because of other reasons I cannot remove the position:relative. Instead, @rtcherry 's solution worked in my case: http://stackoverflow.com/a/20225012/72350 – Diego Jancic May 08 '15 at 17:38
20

I also had this problem and fixed it with the following code:

var wscrolltop = 0;
$sortable_elements.sortable({ 
    start: function(event, ui) {
        wscrolltop = $(window).scrollTop();
    },
    sort: function(event, ui) {                   
        ui.helper.css({'top' : ui.position.top + wscrolltop + 'px'});
    }
});

I discovered, that there still is a problem if you scroll with your sortable-element. Maybe somebody has a solution for this?

UPDATE: The fix is:

$sortable_elements.sortable({ 
    connectWith: '#personal-favs ul.fitems',
    sort: function(event, ui) {  
        ui.helper.css({'top' : ui.position.top + $(window).scrollTop() + 'px'});
    }
});

But still - if you're leaving the list, the sort-event seems to stop.

LPL
  • 16,827
  • 6
  • 51
  • 95
Theodor
  • 201
  • 2
  • 3
9

You also need to account for the fact this is specific to firefox, here is the snippet I'm using - I got directed the right way from Harris' solution. I encountered this problem w/o using the helper when the sortable was in a relatively positioned container.

  var options = { 
   handle: '.mover', 
   update:updateSorting 
 };
  var userAgent = navigator.userAgent.toLowerCase();
  if(userAgent.match(/firefox/)) {
    options["start"] = function (event, ui) { ui.item.css('margin-top', $(window).scrollTop() ); };
    options["beforeStop"] = function (event, ui) { ui.item.css('margin-top', 0 ); };
  }
  $("#" + list_id+"").sortable(options);
  $("#" + list_id+"").disableSelection();

You could also do this check on the server and then have 2 different calls depending on the browser.

Russell
  • 390
  • 4
  • 5
  • Excellent, you saved me a lot of trouble here. – Dan Fuller Jul 28 '10 at 07:33
  • this is great solution for me because i am using jquery 1.4.2 & jquery-ui.1.8.23 so i had encounter this problem so it will fix now. this problem will be also fix using jquery.1.8.js but i was getting so many issue when i am replacing my jquery.1.4.2 to jquery.1.8.js so thnx – Mrugen Ramani Oct 09 '12 at 06:27
  • it makes me sick to have to do this, but this was the only solution (out of like 50235289) that actually worked. Seriously, though, SICK TO MY STOMACH. Sigh. – Jason Oct 15 '12 at 19:45
  • actually fixed the issue by removing the `html { overflow-x: hidden; }` in my CSS. unreal. – Jason Oct 15 '12 at 20:23
7

I managed to figure out a fix for this:

$( "items" ).sortable({
start: function (event, ui) {
 if( ui.helper !== undefined )
  ui.helper.css('position','absolute').css('margin-top', $(window).scrollTop() );
},
beforeStop: function (event, ui) {
 if( ui.offset !== undefined )
  ui.helper.css('margin-top', 0);
},
placeholder: 'placeholder-class'
});

Basically, you need to listen for the sortable's "start" event to add the browser's current scrollTop() value to the helper's position, and then you need to listen for the sortable's "beforeStop" event, to remove that offset before the item is officially placed back into the list at its new position.

Hope that's helpful to someone!

4

I force scrollbars on my site so this scrollable offset problem occurs due to having html { overflow-y: scroll } in my CSS.

As I turn scrollable on and off, I used the following to get around it. I've only tested it in IE8, FF12 and Chrome...

  turnSortingOnOff:function(e) {
    e.preventDefault();

    if (stopOrdering()) {
      // get rid of the hacky css on the html element
      $('html').css({ 'overflow-y': '' });
      $('html').css({ 'margin-right': '' });

      elmt.sortable('disable');
    }
    else if (startOrdering()) {
      // if the body is smaller than the window
      // then there aren't any scrollbars
      // in which case, add a right hand margin of 16px
      // to the html to prevent the site wobbling
      if ($('body').outerHeight() <= $(window).height()) {
        $('html').css({ 'margin-right': 16 });
      }

      // then override the { overflow-y: scroll }
      // in the css by setting it to a nice, safe inherit
      $('html').css({ 'overflow-y': 'inherit' });

      elmt.sortable('enable');
    }
  }

Obviously it's not bulletproof - if the document size changes while sorting then other things will have to be done. However, in my experience it looks less weird under those circumstances.

DaveJenni
  • 1,901
  • 1
  • 12
  • 4
1

I "fixed" this using newer jQuery/jQuery UI versions.

  • jQuery 1.8.0
  • jQuery UI 1.8.23
1

For future readers I ran into a similar problem where the helper element has an offset when dragging inside the scrolled div of a bootstrap dialog. When releasing the dragged object, the animation sends the dragged helper element towards it's new position without considering the scrolled portion of the page, which gives a confusing feedback to users.

In order to keep things working with position:relative and overflow-y:auto in the dialog container, my solution was

1- Add the scrollTop() offset to the margin-top on the helper object in the sort start event;

2- remove the margin-top in the beforeStop event

This fixed the animation from being off after draging, but the helper object is pushed below the cursor while dragging in the scrolled portion in the page. The last fix is

3- Calculate and set the top property of the helper object while dragging (using the sort event) relative to the pointer and the container offset.

$(...).sortable({
...
start: function (event, ui) {  
    ui.helper.css('margin-top', $("#mybootstrap-dialog").scrollTop()); 
},
beforeStop: function (event, ui){ 
    ui.helper.css('margin-top',0); 
},
sort: function(event, ui) {
    var top = event.clientY - $('#my-sortable-ul').offset().top  -  $("#mybootstrap-dialog").scrollTop();
    ui.helper.css({'top' : top + 'px'});
    }
},
...

});

Help this helps

0

Had the same problem. In my case it wasnt possible to use the "overflow:auto"-fix. So this is what I did.

var pos_fixed = 1;        // switch variable (the fix should only be fired ones)
$('.drag').draggable({
 start: function(event, ui){
  pos_fixed = 0;            // we're about to start, set to not fixed yet
 },
 drag: function(event, ui){
  if(pos_fixed == 0){       // check if the fix hasn't been fired for this instance yet
   var help_pos = $(ui.helper).offset().top, // get helpers offset
   targ_pos = $(event.target).offset().top,  // get targets(the dragged elements) offset
   marg = targ_pos-help_pos; // calculate the margin the helper has to have to put it on the same y-position as our target
   $(ui.helper).css('margin-top', marg); // put our helper on the same y-position as the target
   pos_fixed = 1;            // we've fixed it, we don't want it to fire again
  }
 }
});

The fix doesn't care about what browser you are using. It will always make sure that the helper has the same y-offset as the target when the drag starts.

tappad
  • 1
0

For future readers with this problem. I upgraded from jqueryui 1.8 to 1.10.3 and the problem was solved with no css fixes.

http://jqueryui.com/

I also upgraded from jquery 1.8 to 1.10.2

http://jquery.com/

Michael J. Calkins
  • 32,082
  • 15
  • 62
  • 91
0

To summarize your efforts and provide a completed solution. The following seemed to work for Chrome 40+ and Firefox 30+

var isFirefox = /firefox/.test(navigator.userAgent.toLowerCase());
$('#target').sortable({
    connectWith: '#target-holder .elements',
    handle: ".element-header",
    start: function(ev, ui) {
        if( isFirefox ) {
            ui.item.css('margin-top', $(window).scrollTop() );
        }
    },
    sort: function(ev, ui) {
        if( isFirefox) {
            ui.helper.css({'top' : ui.position.top - $(window).scrollTop() + 'px'});
        } else {
            ui.helper.css({'top' : ui.position.top + $(window).scrollTop() + 'px'});
        }
    },
    beforeStop: function (ev, ui) {
        if( isFirefox ) {
            ui.item.css('margin-top', 0 );
        }
    }
});
Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
0

Setting overflow: auto makes Firefox start the drag with the element under the pointer, but it also prevents autoscroll from working properly. You can see that right in the jQuery Sortable example, if you make the window small enough that scrolling is needed.

I had overflow: scroll on the html tag, but even removing that and (I think) all the relative containing elements didn't totally solve the problem (meaning the drag starts correctly and autoscroll works). I also tried a mozilla-sniffing patch to _findRelativeOffset (I think that was it), but it didn't help.

What did help for my use case was just dragging with a clone (helper: 'clone' in the draggable constructor). To make it look like the clone wasn't there, I added start and stop methods that just set the visibilty to hidden and then back.

I would have commented above, but I don't have the points yet.

Dan Chadwick
  • 361
  • 3
  • 7
-1

My solution: ".sortable" to add the "position: relative"

$(".sortable").sortable({
      sort: function(event, ui) {
           ui.position.top = ui.position.top + $(window).scrollTop();
      },
});

PS used jQuery UI 1.12.0 and jQuery 2.2.4

Ankly
  • 1
  • 1
-2

sort: function(e, ui){
        var tempVariable = $(ui.item.get(0));
        tempVariable.css('top', (e.clientY)-(tempVariable.css("height").replace("px", "")));
    }
JJJ
  • 32,902
  • 20
  • 89
  • 102
Mirha
  • 1